diff --git a/address/src/test/java/org/ton/java/address/TestAddress.java b/address/src/test/java/org/ton/java/address/TestAddress.java index ed5e6860..93e86d8a 100644 --- a/address/src/test/java/org/ton/java/address/TestAddress.java +++ b/address/src/test/java/org/ton/java/address/TestAddress.java @@ -1,16 +1,15 @@ package org.ton.java.address; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.Assert.assertThrows; + +import java.io.IOException; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.ton.java.utils.Utils; -import java.io.IOException; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.Assert.assertThrows; - @Slf4j @RunWith(JUnit4.class) public class TestAddress { @@ -107,7 +106,7 @@ public void testCompareAddress() { public void testSaveAddress() throws IOException { // wc 0 Address address01 = Address.of(TEST_ADDRESS_0); - log.info("full address " + address01.toString(false)); + log.info("full address {}" , address01.toString(false)); log.info("bounceable address {}", address01.toString(true, true, true)); log.info("non-bounceable address {}", address01.toString(true, true, false)); diff --git a/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java b/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java index 3390118d..6753428b 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java +++ b/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java @@ -1,11 +1,11 @@ package org.ton.java.tlb.types; import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; import java.math.BigInteger; import lombok.Builder; import lombok.Data; +import org.apache.commons.lang3.StringUtils; import org.ton.java.utils.Utils; @Builder @@ -66,7 +66,7 @@ public static void printMessageFeesFooter() { } public String getSrc() { - if (nonNull(src)) { + if (StringUtils.isNotEmpty(src)) { return src.substring(0, 7) + "..." + src.substring(src.length() - 6, src.length() - 1); } else { return "N/A"; @@ -74,7 +74,7 @@ public String getSrc() { } public String getDst() { - if (nonNull(dst)) { + if (StringUtils.isNotEmpty(dst)) { return dst.substring(0, 7) + "..." + dst.substring(dst.length() - 6, dst.length() - 1); } else { return "N/A"; diff --git a/cell/src/main/java/org/ton/java/tlb/types/Transaction.java b/cell/src/main/java/org/ton/java/tlb/types/Transaction.java index bd1a8462..c2838fad 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/Transaction.java +++ b/cell/src/main/java/org/ton/java/tlb/types/Transaction.java @@ -61,7 +61,7 @@ private String getMagic() { return Long.toBinaryString(magic); } - private String getAccountAddr() { + private String getAccountAddrShort() { if (nonNull(accountAddr)) { String str64 = StringUtils.leftPad(accountAddr.toString(16), 64, "0"); return str64.substring(0, 5) @@ -191,7 +191,8 @@ public static AccountStates deserializeAccountState(byte state) { public TransactionFees getTransactionFees() { Transaction tx = this; - BigInteger totalFees = tx.getTotalFees().getCoins(); + BigInteger totalFees = + nonNull(tx.getTotalFees()) ? tx.getTotalFees().getCoins() : BigInteger.ZERO; BigInteger totalForwardFees = getForwardFees(tx.getDescription()); BigInteger computeFees = getComputeFees(tx.getDescription()); @@ -206,26 +207,28 @@ public TransactionFees getTransactionFees() { BigInteger lt = tx.getLt(); long outMsgs = tx.getOutMsgCount(); - Message inMsg = tx.getInOut().getIn(); - if (nonNull(inMsg)) { - Cell body = inMsg.getBody(); + if (nonNull(tx.getInOut())) { + Message inMsg = tx.getInOut().getIn(); + if (nonNull(inMsg)) { + Cell body = inMsg.getBody(); - if (nonNull(body) && body.getBits().getUsedBits() >= 32) { - op = CellSlice.beginParse(body).preloadInt(32); - } else { - op = BigInteger.ONE.negate(); - } + if (nonNull(body) && body.getBits().getUsedBits() >= 32) { + op = CellSlice.beginParse(body).preloadInt(32); + } else { + op = BigInteger.ONE.negate(); + } - if (inMsg.getInfo() instanceof InternalMessageInfo) { - valueIn = ((InternalMessageInfo) inMsg.getInfo()).getValue().getCoins(); - inForwardFees = ((InternalMessageInfo) inMsg.getInfo()).getFwdFee(); + if (inMsg.getInfo() instanceof InternalMessageInfo) { + valueIn = ((InternalMessageInfo) inMsg.getInfo()).getValue().getCoins(); + inForwardFees = ((InternalMessageInfo) inMsg.getInfo()).getFwdFee(); + } } - } - for (Message outMsg : tx.getInOut().getOutMessages()) { - if (outMsg.getInfo() instanceof InternalMessageInfo) { - InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo(); - valueOut = valueOut.add(intMsgInfo.getValue().getCoins()); + for (Message outMsg : tx.getInOut().getOutMessages()) { + if (outMsg.getInfo() instanceof InternalMessageInfo) { + InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo(); + valueOut = valueOut.add(intMsgInfo.getValue().getCoins()); + } } } @@ -234,7 +237,10 @@ public TransactionFees getTransactionFees() { (isNull(op)) ? "N/A" : (op.compareTo(BigInteger.ONE.negate()) != 0) ? op.toString(16) : "no body") - .type(tx.getDescription().getClass().getSimpleName().substring(22)) + .type( + nonNull(tx.getDescription()) + ? tx.getDescription().getClass().getSimpleName().substring(22) + : "") .valueIn(valueIn) .valueOut(valueOut) .totalFees(totalFees) @@ -246,7 +252,7 @@ public TransactionFees getTransactionFees() { .totalActions(totalActions) .now(now) .lt(lt) - .account(tx.getAccountAddr()) + .account(nonNull(tx.getAccountAddr()) ? tx.getAccountAddrShort() : "") .build(); } @@ -396,151 +402,153 @@ public List getAllMessageFees() { BigInteger op; - Message inMsg = tx.getInOut().getIn(); - if (nonNull(inMsg)) { - Cell body = inMsg.getBody(); + if (nonNull(tx.getInOut())) { - if (nonNull(body) && body.getBits().getUsedBits() >= 32) { - op = CellSlice.beginParse(body).preloadInt(32); - } else { - op = BigInteger.ONE.negate(); - } + Message inMsg = tx.getInOut().getIn(); + if (nonNull(inMsg)) { + Cell body = inMsg.getBody(); - if (inMsg.getInfo() instanceof InternalMessageInfo) { - InternalMessageInfo msgInfo = ((InternalMessageInfo) inMsg.getInfo()); - MessageFees msgFee = - MessageFees.builder() - .direction("in") - .type(formatMsgType(inMsg.getInfo().getClass().getSimpleName())) - .op( - (isNull(op)) - ? "N/A" - : (op.compareTo(BigInteger.ONE.negate()) != 0) - ? op.toString(16) - : "no body") - .value(msgInfo.getValue().getCoins()) - .fwdFee(msgInfo.getFwdFee()) - .ihrFee(msgInfo.getIHRFee()) - .createdLt(msgInfo.getCreatedLt()) - .createdAt(BigInteger.valueOf(msgInfo.getCreatedAt())) - .src(msgInfo.getSrcAddr().toAddress().toRaw()) - .dst(msgInfo.getDstAddr().toAddress().toRaw()) - .build(); - messageFees.add(msgFee); - } - if (inMsg.getInfo() instanceof ExternalMessageOutInfo) { - ExternalMessageOutInfo msgInfo = ((ExternalMessageOutInfo) inMsg.getInfo()); - MessageFees msgFee = - MessageFees.builder() - .direction("in") - .type(formatMsgType(inMsg.getInfo().getClass().getSimpleName())) - .op( - (isNull(op)) - ? "N/A" - : (op.compareTo(BigInteger.ONE.negate()) != 0) - ? op.toString(16) - : "no body") - .createdLt(msgInfo.getCreatedLt()) - .createdAt(BigInteger.valueOf(msgInfo.getCreatedAt())) - .src(msgInfo.getSrcAddr().toAddress().toRaw()) - .dst(msgInfo.getDstAddr().toString()) - .build(); - messageFees.add(msgFee); - } - if (inMsg.getInfo() instanceof ExternalMessageInInfo) { - ExternalMessageInInfo msgInfo = ((ExternalMessageInInfo) inMsg.getInfo()); - MessageFees msgFee = - MessageFees.builder() - .direction("in") - .type(formatMsgType(inMsg.getInfo().getClass().getSimpleName())) - .op( - (isNull(op)) - ? "N/A" - : (op.compareTo(BigInteger.ONE.negate()) != 0) - ? op.toString(16) - : "no body") - .importFee(msgInfo.getImportFee()) - .src(msgInfo.getSrcAddr().toString()) - .dst(msgInfo.getDstAddr().toString()) - .build(); - messageFees.add(msgFee); + if (nonNull(body) && body.getBits().getUsedBits() >= 32) { + op = CellSlice.beginParse(body).preloadInt(32); + } else { + op = BigInteger.ONE.negate(); + } + + if (inMsg.getInfo() instanceof InternalMessageInfo) { + InternalMessageInfo msgInfo = ((InternalMessageInfo) inMsg.getInfo()); + MessageFees msgFee = + MessageFees.builder() + .direction("in") + .type(formatMsgType(inMsg.getInfo().getClass().getSimpleName())) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .value(msgInfo.getValue().getCoins()) + .fwdFee(msgInfo.getFwdFee()) + .ihrFee(msgInfo.getIHRFee()) + .createdLt(msgInfo.getCreatedLt()) + .createdAt(BigInteger.valueOf(msgInfo.getCreatedAt())) + .src(msgInfo.getSrcAddr().toAddress().toRaw()) + .dst(msgInfo.getDstAddr().toAddress().toRaw()) + .build(); + messageFees.add(msgFee); + } + if (inMsg.getInfo() instanceof ExternalMessageOutInfo) { + ExternalMessageOutInfo msgInfo = ((ExternalMessageOutInfo) inMsg.getInfo()); + MessageFees msgFee = + MessageFees.builder() + .direction("in") + .type(formatMsgType(inMsg.getInfo().getClass().getSimpleName())) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .createdLt(msgInfo.getCreatedLt()) + .createdAt(BigInteger.valueOf(msgInfo.getCreatedAt())) + .src(msgInfo.getSrcAddr().toAddress().toRaw()) + .dst(msgInfo.getDstAddr().toString()) + .build(); + messageFees.add(msgFee); + } + if (inMsg.getInfo() instanceof ExternalMessageInInfo) { + ExternalMessageInInfo msgInfo = ((ExternalMessageInInfo) inMsg.getInfo()); + MessageFees msgFee = + MessageFees.builder() + .direction("in") + .type(formatMsgType(inMsg.getInfo().getClass().getSimpleName())) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .importFee(msgInfo.getImportFee()) + .src(msgInfo.getSrcAddr().toString()) + .dst(msgInfo.getDstAddr().toString()) + .build(); + messageFees.add(msgFee); + } } - } - for (Message outMsg : tx.getInOut().getOutMessages()) { + for (Message outMsg : tx.getInOut().getOutMessages()) { - Cell body = outMsg.getBody(); + Cell body = outMsg.getBody(); - if (nonNull(body) && body.getBits().getUsedBits() >= 32) { - op = CellSlice.beginParse(body).preloadInt(32); - } else { - op = BigInteger.ONE.negate(); - } + if (nonNull(body) && body.getBits().getUsedBits() >= 32) { + op = CellSlice.beginParse(body).preloadInt(32); + } else { + op = BigInteger.ONE.negate(); + } - if (outMsg.getInfo() instanceof InternalMessageInfo) { - InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo(); - - MessageFees msgFee = - MessageFees.builder() - .direction("out") - .type(formatMsgType(outMsg.getInfo().getClass().getSimpleName())) - .op( - (isNull(op)) - ? "N/A" - : (op.compareTo(BigInteger.ONE.negate()) != 0) - ? op.toString(16) - : "no body") - .value(intMsgInfo.getValue().getCoins()) - .fwdFee(intMsgInfo.getFwdFee()) - .ihrFee(intMsgInfo.getIHRFee()) - .createdLt(intMsgInfo.getCreatedLt()) - .createdAt(BigInteger.valueOf(intMsgInfo.getCreatedAt())) - .src(intMsgInfo.getSrcAddr().toAddress().toRaw()) - .dst(intMsgInfo.getDstAddr().toAddress().toRaw()) - .build(); - messageFees.add(msgFee); - } - if (outMsg.getInfo() instanceof ExternalMessageOutInfo) { - ExternalMessageOutInfo outMsgInfo = (ExternalMessageOutInfo) outMsg.getInfo(); - - MessageFees msgFee = - MessageFees.builder() - .direction("out") - .type(formatMsgType(outMsg.getInfo().getClass().getSimpleName())) - .op( - (isNull(op)) - ? "N/A" - : (op.compareTo(BigInteger.ONE.negate()) != 0) - ? op.toString(16) - : "no body") - .createdLt(outMsgInfo.getCreatedLt()) - .createdAt(BigInteger.valueOf(outMsgInfo.getCreatedAt())) - .src(outMsgInfo.getSrcAddr().toAddress().toRaw()) - .dst(outMsgInfo.getDstAddr().toString()) - .build(); - messageFees.add(msgFee); - } - if (outMsg.getInfo() instanceof ExternalMessageInInfo) { - ExternalMessageInInfo outMsgInfo = (ExternalMessageInInfo) outMsg.getInfo(); - - MessageFees msgFee = - MessageFees.builder() - .direction("out") - .type(formatMsgType(outMsg.getInfo().getClass().getSimpleName())) - .op( - (isNull(op)) - ? "N/A" - : (op.compareTo(BigInteger.ONE.negate()) != 0) - ? op.toString(16) - : "no body") - .importFee(outMsgInfo.getImportFee()) - .src(outMsgInfo.getSrcAddr().toString()) - .dst(outMsgInfo.getDstAddr().toString()) - .build(); - messageFees.add(msgFee); + if (outMsg.getInfo() instanceof InternalMessageInfo) { + InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo(); + + MessageFees msgFee = + MessageFees.builder() + .direction("out") + .type(formatMsgType(outMsg.getInfo().getClass().getSimpleName())) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .value(intMsgInfo.getValue().getCoins()) + .fwdFee(intMsgInfo.getFwdFee()) + .ihrFee(intMsgInfo.getIHRFee()) + .createdLt(intMsgInfo.getCreatedLt()) + .createdAt(BigInteger.valueOf(intMsgInfo.getCreatedAt())) + .src(intMsgInfo.getSrcAddr().toAddress().toRaw()) + .dst(intMsgInfo.getDstAddr().toAddress().toRaw()) + .build(); + messageFees.add(msgFee); + } + if (outMsg.getInfo() instanceof ExternalMessageOutInfo) { + ExternalMessageOutInfo outMsgInfo = (ExternalMessageOutInfo) outMsg.getInfo(); + + MessageFees msgFee = + MessageFees.builder() + .direction("out") + .type(formatMsgType(outMsg.getInfo().getClass().getSimpleName())) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .createdLt(outMsgInfo.getCreatedLt()) + .createdAt(BigInteger.valueOf(outMsgInfo.getCreatedAt())) + .src(outMsgInfo.getSrcAddr().toAddress().toRaw()) + .dst(outMsgInfo.getDstAddr().toString()) + .build(); + messageFees.add(msgFee); + } + if (outMsg.getInfo() instanceof ExternalMessageInInfo) { + ExternalMessageInInfo outMsgInfo = (ExternalMessageInInfo) outMsg.getInfo(); + + MessageFees msgFee = + MessageFees.builder() + .direction("out") + .type(formatMsgType(outMsg.getInfo().getClass().getSimpleName())) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .importFee(outMsgInfo.getImportFee()) + .src(outMsgInfo.getSrcAddr().toString()) + .dst(outMsgInfo.getDstAddr().toString()) + .build(); + messageFees.add(msgFee); + } } } - return messageFees; } diff --git a/devenv/src/main/resources/archetype-resources/pom.xml b/devenv/src/main/resources/archetype-resources/pom.xml index cdf6573a..0b8a88bd 100644 --- a/devenv/src/main/resources/archetype-resources/pom.xml +++ b/devenv/src/main/resources/archetype-resources/pom.xml @@ -42,5 +42,11 @@ 4.13.2 test + + org.assertj + assertj-core + 3.23.1 + test + \ No newline at end of file diff --git a/devenv/src/main/resources/archetype-resources/src/test/java/DevEnvTest.java b/devenv/src/main/resources/archetype-resources/src/test/java/DevEnvTest.java index 36f0da1f..0f3cabd1 100644 --- a/devenv/src/main/resources/archetype-resources/src/test/java/DevEnvTest.java +++ b/devenv/src/main/resources/archetype-resources/src/test/java/DevEnvTest.java @@ -1,21 +1,27 @@ +import static org.assertj.core.api.Assertions.assertThat; + import com.iwebpp.crypto.TweetNaclFast; -import java.util.Collections; +import java.math.BigInteger; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.ton.java.address.Address; import org.ton.java.cell.Cell; +import org.ton.java.cell.CellBuilder; import org.ton.java.emulator.EmulateTransactionResult; +import org.ton.java.emulator.tvm.TvmEmulator; +import org.ton.java.emulator.tvm.TvmVerbosityLevel; import org.ton.java.emulator.tx.TxEmulator; import org.ton.java.emulator.tx.TxEmulatorConfig; import org.ton.java.emulator.tx.TxVerbosityLevel; import org.ton.java.smartcontract.*; +import org.ton.java.smartcontract.GenericSmartContract; import org.ton.java.smartcontract.SmartContractCompiler; -import org.ton.java.smartcontract.types.Destination; -import org.ton.java.smartcontract.types.WalletV5Config; -import org.ton.java.smartcontract.wallet.v5.WalletV5; +import org.ton.java.smartcontract.faucet.TestnetFaucet; +import org.ton.java.smartcontract.utils.MsgUtils; import org.ton.java.tlb.types.Message; +import org.ton.java.tlb.types.ShardAccount; import org.ton.java.tlb.types.StateInit; import org.ton.java.tonlib.Tonlib; import org.ton.java.tonlib.types.BlockIdExt; @@ -25,23 +31,25 @@ @RunWith(JUnit4.class) public class DevEnvTest { + Cell codeCell; + Cell dataCell; + StateInit stateInit; + + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + Address dummyAddress = Address.of("EQAyjRKDnEpTBNfRHqYdnzGEQjdY4KG3gxgqiG3DpDY46u8G"); + @Test - public void testCompileContract() { + public void testCompileSmartContract() { SmartContractCompiler smcFunc = SmartContractCompiler.builder().contractAsResource("/simple.fc").build(); - Cell codeCell = smcFunc.compileToCell(); - log.info("codeCell {}", codeCell.print()); - } - @Test - public void testTonlibGetLastBlock() { - Tonlib tonlib = Tonlib.builder().testnet(true).build(); - BlockIdExt block = tonlib.getLast().getLast(); - log.info("block {}", block); + assertThat(smcFunc.compile()).isNotNull(); } @Test - public void testTxEmulatorWalletV5ExternalMsgSimplified() { + public void testSendExternalMessageInEmulator() { + + compile(); TxEmulator txEmulator = TxEmulator.builder() @@ -49,62 +57,161 @@ public void testTxEmulatorWalletV5ExternalMsgSimplified() { .verbosityLevel(TxVerbosityLevel.UNLIMITED) .build(); - SmartContractCompiler smcFunc = - SmartContractCompiler.builder().contractAsResource("/new-wallet-v5.fc").build(); + txEmulator.setDebugEnabled(true); - Cell codeCell = smcFunc.compileToCell(); + double initialBalanceInToncoins = 0.1; - byte[] publicKey = - Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); - byte[] secretKey = - Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); - TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); + Cell bodyCell = + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .endCell(); - WalletV5 walletV5 = - WalletV5.builder() - .keyPair(keyPair) - .isSigAuthAllowed(false) - .initialSeqno(0) - .walletId(42) + Message extMsg = MsgUtils.createExternalMessage(dummyAddress, null, bodyCell); + + EmulateTransactionResult result = + txEmulator.emulateTransaction( + codeCell, dataCell, Utils.toNano(initialBalanceInToncoins), extMsg.toCell().toBase64()); + + ShardAccount newShardAccount = result.getNewShardAccount(); + log.info("result sendExternalMessage[1]: {}", result); + result.getTransaction().printTransactionFees(true, true); + result.getTransaction().printAllMessages(true); + log.info("end balance after #1 tx: {}", Utils.formatNanoValue(newShardAccount.getBalance().toString())); + + bodyCell = + CellBuilder.beginCell() + .storeUint(1, 32) // increased seqno + .endCell(); + + extMsg = MsgUtils.createExternalMessage(dummyAddress, null, bodyCell); + + result = txEmulator.emulateTransaction(newShardAccount.toCell().toBase64(), extMsg.toCell().toBase64()); + result.getTransaction().printTransactionFees(true, true); + result.getTransaction().printAllMessages(true); + log.info("end balance after #2 tx: {}", Utils.formatNanoValue(result.getNewShardAccount().getBalance().toString())); + + TvmEmulator tvmEmulator = + TvmEmulator.builder() + .codeBoc(codeCell.toBase64()) + .dataBoc(result.getNewStateInit().getData().toBase64()) + .verbosityLevel(TvmVerbosityLevel.UNLIMITED) .build(); - Cell dataCell = walletV5.createDataCell(); - - log.info("codeCellHex {}", codeCell.toHex()); - log.info("dataCellHex {}", dataCell.toHex()); - - StateInit walletV5StateInit = StateInit.builder().code(codeCell).data(dataCell).build(); - - Address address = walletV5StateInit.getAddress(); - log.info("addressRaw {}", address.toRaw()); - log.info("addressBounceable {}", address.toBounceable()); - - String rawDummyDestinationAddress = - "0:258e549638a6980ae5d3c76382afd3f4f32e34482dafc3751e3358589c8de00d"; - - WalletV5Config walletV5Config = - WalletV5Config.builder() - .seqno(0) - .walletId(42) - .body( - walletV5 - .createBulkTransfer( - Collections.singletonList( - Destination.builder() - .bounce(false) - .address(rawDummyDestinationAddress) - .amount(Utils.toNano(1)) - .build())) - .toCell()) + log.info("updated seqno {}",tvmEmulator.runGetSeqNo()); + assertThat(tvmEmulator.runGetSeqNo()).isEqualTo(2); + } + + @Test + public void testNewContractInEmulatorSendInternalMessage() { + + compile(); + + TxEmulator txEmulator = + TxEmulator.builder() + .configType(TxEmulatorConfig.TESTNET) + .verbosityLevel(TxVerbosityLevel.UNLIMITED) .build(); - Message extMsg = walletV5.prepareExternalMsg(walletV5Config); + txEmulator.setDebugEnabled(true); + + double initialBalanceInToncoins = 1; + + Cell bodyCell = + CellBuilder.beginCell() + .storeUint(3, 32) // seqno + .endCell(); + + Message internalMessageMsg = + MsgUtils.createInternalMessageWithSourceAddress(dummyAddress, stateInit.getAddress(), Utils.toNano(0.1), null, bodyCell, false); EmulateTransactionResult result = txEmulator.emulateTransaction( - codeCell, dataCell, Utils.toNano(2), extMsg.toCell().toBase64()); + codeCell, + dataCell, + Utils.toNano(initialBalanceInToncoins), + internalMessageMsg.toCell().toBase64()); - log.info("result sendExternalMessage[1]: {}", result); - result.getTransaction().printTransactionFees(true); + log.info("result {}", result); + + result.getTransaction().printTransactionFees(true, true); + result.getTransaction().printAllMessages(true); + + assertThat(result.isSuccess()).isTrue(); + + + TvmEmulator tvmEmulator = + TvmEmulator.builder() + .codeBoc(codeCell.toBase64()) + .dataBoc(result.getNewStateInit().getData().toBase64()) + .verbosityLevel(TvmVerbosityLevel.UNLIMITED) + .build(); + + log.info("updated seqno {}",tvmEmulator.runGetSeqNo()); + assertThat(tvmEmulator.runGetSeqNo()).isEqualTo(3); + } + + @Test + public void testDeployContractInTestnet() throws InterruptedException { + + compile(); + + Tonlib tonlib = Tonlib.builder().testnet(true).ignoreCache(false).build(); + + GenericSmartContract smc = + GenericSmartContract.builder() + .tonlib(tonlib) + .keyPair(keyPair) + .code(codeCell.toHex()) + .data(dataCell.toHex()) + .build(); + + double initialBalanceInToncoins = 0.1; + + BigInteger balance = + TestnetFaucet.topUpContract( + tonlib, Address.of(smc.getAddress().toNonBounceableTestnet()), Utils.toNano(initialBalanceInToncoins)); + log.info("new wallet balance {}", Utils.formatNanoValue(balance)); + + Cell deployMessageBody = + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .endCell(); + + assertThat(smc.deployWithoutSignature(deployMessageBody)).isNotNull(); + smc.waitForDeployment(20); + } + + @Test + public void testTonlibGetLastBlock() { + Tonlib tonlib = Tonlib.builder().testnet(true).build(); + BlockIdExt block = tonlib.getLast().getLast(); + log.info("block {}", block); + } + + private void compile() { + SmartContractCompiler smcFunc = + SmartContractCompiler.builder().contractAsResource("/simple.fc").build(); + + codeCell = smcFunc.compileToCell(); + + dataCell = + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .endCell(); + + GenericSmartContract smc = + GenericSmartContract.builder().keyPair(keyPair).code(codeCell.toHex()).data(dataCell.toHex()).build(); + + String nonBounceableAddress = smc.getAddress().toNonBounceable(); + String bounceableAddress = smc.getAddress().toBounceable(); + String rawAddress = smc.getAddress().toRaw(); + + log.info("non-bounceable address: {}", nonBounceableAddress); + log.info(" bounceable address: {}", bounceableAddress); + log.info(" raw address: {}", rawAddress); + log.info("pub-key {}", Utils.bytesToHex(smc.getKeyPair().getPublicKey())); + log.info("prv-key {}", Utils.bytesToHex(smc.getKeyPair().getSecretKey())); + + stateInit = StateInit.builder().code(codeCell).data(dataCell).build(); } } diff --git a/devenv/src/main/resources/archetype-resources/src/test/resources/new-wallet-v5.fc b/devenv/src/main/resources/archetype-resources/src/test/resources/new-wallet-v5.fc deleted file mode 100644 index a9407462..00000000 --- a/devenv/src/main/resources/archetype-resources/src/test/resources/new-wallet-v5.fc +++ /dev/null @@ -1,301 +0,0 @@ -#pragma version =0.4.4; - -#include "stdlib.fc"; - -const int error::signature_disabled = 132; -const int error::invalid_seqno = 133; -const int error::invalid_wallet_id = 134; -const int error::invalid_signature = 135; -const int error::expired = 136; -const int error::external_send_message_must_have_ignore_errors_send_mode = 137; -const int error::invalid_message_operation = 138; -const int error::add_extension = 139; -const int error::remove_extension = 140; -const int error::unsupported_action = 141; -const int error::disable_signature_when_extensions_is_empty = 142; -const int error::this_signature_mode_already_set = 143; -const int error::remove_last_extension_when_signature_disabled = 144; -const int error::extension_wrong_workchain = 145; -const int error::only_extension_can_change_signature_mode = 146; -const int error::invalid_c5 = 147; - -const int size::bool = 1; -const int size::seqno = 32; -const int size::wallet_id = 32; -const int size::public_key = 256; -const int size::valid_until = 32; -const int size::message_flags = 4; -const int size::signature = 512; -const int size::message_operation_prefix = 32; -const int size::address_hash_size = 256; -const int size::query_id = 64; - -const int prefix::signed_external = 0x7369676E; -const int prefix::signed_internal = 0x73696E74; -const int prefix::extension_action = 0x6578746E; - -(slice, int) check_and_remove_add_extension_prefix(slice body) impure asm "x{02} SDBEGINSQ"; -(slice, int) check_and_remove_remove_extension_prefix(slice body) impure asm "x{03} SDBEGINSQ"; -(slice, int) check_and_remove_set_signature_allowed_prefix(slice body) impure asm "x{04} SDBEGINSQ"; - -;;; returns the number of trailing zeroes in slice s. -int count_trailing_zeroes(slice s) asm "SDCNTTRAIL0"; - -;;; returns the last 0 ≤ l ≤ 1023 bits of s. -slice get_last_bits(slice s, int l) asm "SDCUTLAST"; -;;; returns all but the last 0 ≤ l ≤ 1023 bits of s. -slice remove_last_bits(slice s, int l) asm "SDSKIPLAST"; - -;; `action_send_msg` has 0x0ec3c86d prefix -;; https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L380 -slice enforce_and_remove_action_send_msg_prefix(slice body) impure asm "x{0ec3c86d} SDBEGINS"; - -;;; put raw list of OutActions to C5 register. -;;; OutList TLB-schema - https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L378 -;;; C5 register - https://docs.ton.org/tvm.pdf, page 11 -() set_c5_actions(cell action_list) impure asm "c5 POP"; - -;;; transforms an ordinary or exotic cell into a Slice, as if it were an ordinary cell. A flag is returned indicating whether c is exotic. If that be the case, its type can later be deserialized from the first eight bits of s. -(slice, int) begin_parse_raw(cell c) asm "XCTOS"; - -cell verify_c5_actions(cell c5, int is_external) inline { - ;; XCTOS doesn't automatically load exotic cells (unlike CTOS `begin_parse`). - ;; we use it in `verify_c5_actions` because during action phase processing exotic cells in c5 won't be unfolded too. - ;; exotic cell starts with 0x02, 0x03 or 0x04 so it will not pass action_send_msg prefix check - (slice cs, _) = c5.begin_parse_raw(); - - int count = 0; - - while (~ cs.slice_empty?()) { - ;; only `action_send_msg` is allowed; `action_set_code`, `action_reserve_currency` or `action_change_library` are not. - cs = cs.enforce_and_remove_action_send_msg_prefix(); - - throw_unless(error::invalid_c5, cs.slice_bits() == 8); ;; send_mode uint8 - throw_unless(error::invalid_c5, cs.slice_refs() == 2); ;; next-action-ref and MessageRelaxed ref - - ;; enforce that send_mode has +2 bit (ignore errors) set for external message. - ;; if such send_mode is not set and sending fails at the action phase (for example due to insufficient balance) then the seqno will not be increased and the external message will be processed again and again. - - ;; action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; - ;; https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L380 - ;; load 7 bits and make sure that they end with 1 - throw_if(error::external_send_message_must_have_ignore_errors_send_mode, is_external & (count_trailing_zeroes(cs.preload_bits(7)) > 0)); - - (cs, _) = cs.preload_ref().begin_parse_raw(); - count += 1; - } - throw_unless(error::invalid_c5, count <= 255); - throw_unless(error::invalid_c5, cs.slice_refs() == 0); - - return c5; -} - -() process_actions(slice cs, int is_external, int is_extension) impure inline_ref { - cell c5_actions = cs~load_maybe_ref(); - ifnot (cell_null?(c5_actions)) { - ;; Simply set the C5 register with all pre-computed actions after verification: - set_c5_actions(c5_actions.verify_c5_actions(is_external)); - } - if (cs~load_int(1) == 0) { ;; has_other_actions - return (); - } - - ;; Loop extended actions - while (true) { - int is_add_extension = cs~check_and_remove_add_extension_prefix(); - int is_remove_extension = is_add_extension ? 0 : cs~check_and_remove_remove_extension_prefix(); - ;; Add/remove extensions - if (is_add_extension | is_remove_extension) { - (int address_wc, int address_hash) = parse_std_addr(cs~load_msg_addr()); - (int my_address_wc, _) = parse_std_addr(my_address()); - - throw_unless(error::extension_wrong_workchain, my_address_wc == address_wc); ;; the extension must be in the same workchain as the wallet. - - slice data_slice = get_data().begin_parse(); - slice data_slice_before_extensions = data_slice~load_bits(size::bool + size::seqno + size::wallet_id + size::public_key); - cell extensions = data_slice.preload_dict(); - - ;; Add extension - if (is_add_extension) { - (extensions, int is_success) = extensions.udict_add_builder?(size::address_hash_size, address_hash, begin_cell().store_int(-1, 1)); - throw_unless(error::add_extension, is_success); - } else { ;; Remove extension - (extensions, int is_success) = extensions.udict_delete?(size::address_hash_size, address_hash); - throw_unless(error::remove_extension, is_success); - int is_signature_allowed = data_slice_before_extensions.preload_int(size::bool); - throw_if(error::remove_last_extension_when_signature_disabled, null?(extensions) & (~ is_signature_allowed)); - } - - set_data(begin_cell() - .store_slice(data_slice_before_extensions) - .store_dict(extensions) - .end_cell()); - - } elseif (cs~check_and_remove_set_signature_allowed_prefix()) { ;; allow/disallow signature - throw_unless(error::only_extension_can_change_signature_mode, is_extension); - int allow_signature = cs~load_int(1); - slice data_slice = get_data().begin_parse(); - int is_signature_allowed = data_slice~load_int(size::bool); - throw_if(error::this_signature_mode_already_set, is_signature_allowed == allow_signature); - is_signature_allowed = allow_signature; - - slice data_tail = data_slice; ;; seqno, wallet_id, public_key, extensions - ifnot (allow_signature) { ;; disallow - int is_extensions_not_empty = data_slice.skip_bits(size::seqno + size::wallet_id + size::public_key).preload_int(1); - throw_unless(error::disable_signature_when_extensions_is_empty, is_extensions_not_empty); - } - - set_data(begin_cell() - .store_int(is_signature_allowed, size::bool) - .store_slice(data_tail) ;; seqno, wallet_id, public_key, extensions - .end_cell()); - } else { - throw(error::unsupported_action); - } - ifnot (cs.slice_refs()) { - return (); - } - cs = cs.preload_ref().begin_parse(); - } -} - -;; ------------------------------------------------------------------------------------------------ - -() process_signed_request(slice in_msg_body, int is_external) impure inline { - slice signature = in_msg_body.get_last_bits(size::signature); - slice signed_slice = in_msg_body.remove_last_bits(size::signature); - - slice cs = signed_slice.skip_bits(size::message_operation_prefix); ;; skip signed_internal or signed_external prefix - (int wallet_id, int valid_until, int seqno) = (cs~load_uint(size::wallet_id), cs~load_uint(size::valid_until), cs~load_uint(size::seqno)); - - slice data_slice = get_data().begin_parse(); - int is_signature_allowed = data_slice~load_int(size::bool); - int stored_seqno = data_slice~load_uint(size::seqno); - slice data_tail = data_slice; ;; wallet_id, public_key, extensions - int stored_wallet_id = data_slice~load_uint(size::wallet_id); - int public_key = data_slice~load_uint(size::public_key); - int is_extensions_not_empty = data_slice.preload_int(1); - - int is_signature_valid = check_signature(slice_hash(signed_slice), signature, public_key); - ifnot (is_signature_valid) { - if (is_external) { - throw(error::invalid_signature); - } else { - return (); - } - } - ;; In case the wallet application has initially, by mistake, deployed a contract with the wrong bit (signature is forbidden and extensions are empty) - we allow such a contract to work. - throw_if(error::signature_disabled, (~ is_signature_allowed) & is_extensions_not_empty); - throw_unless(error::invalid_seqno, seqno == stored_seqno); - throw_unless(error::invalid_wallet_id, wallet_id == stored_wallet_id); - throw_if(error::expired, valid_until <= now()); - - if (is_external) { - accept_message(); - } - - stored_seqno = stored_seqno + 1; - set_data(begin_cell() - .store_int(true, size::bool) ;; is_signature_allowed - .store_uint(stored_seqno, size::seqno) - .store_slice(data_tail) ;; wallet_id, public_key, extensions - .end_cell()); - - if (is_external) { - ;; For external messages we commit seqno changes, so that even if an exception occurs further on, the reply-protection will still work. - commit(); - } - - process_actions(cs, is_external, false); -} - -() recv_external(slice in_msg_body) impure inline { - throw_unless(error::invalid_message_operation, in_msg_body.preload_uint(size::message_operation_prefix) == prefix::signed_external); - process_signed_request(in_msg_body, true); -} - -;; ------------------------------------------------------------------------------------------------ - -() recv_internal(cell in_msg_full, slice in_msg_body) impure inline { - if (in_msg_body.slice_bits() < size::message_operation_prefix) { - return (); ;; just receive Toncoins - } - int op = in_msg_body.preload_uint(size::message_operation_prefix); - if ((op != prefix::extension_action) & (op != prefix::signed_internal)) { - return (); ;; just receive Toncoins - } - - ;; bounced messages has 0xffffffff prefix and skipped by op check - - if (op == prefix::extension_action) { - in_msg_body~skip_bits(size::message_operation_prefix); - - slice in_msg_full_slice = in_msg_full.begin_parse(); - in_msg_full_slice~skip_bits(size::message_flags); - ;; Authenticate extension by its address. - (int sender_address_wc, int sender_address_hash) = parse_std_addr(in_msg_full_slice~load_msg_addr()); - (int my_address_wc, _) = parse_std_addr(my_address()); - - if (my_address_wc != sender_address_wc) { - return (); - } - - cell extensions = get_data().begin_parse() - .skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key) - .preload_dict(); - - ;; Note that some random contract may have deposited funds with this prefix, - ;; so we accept the funds silently instead of throwing an error (wallet v4 does the same). - (_, int extension_found) = extensions.udict_get?(size::address_hash_size, sender_address_hash); - ifnot (extension_found) { - return (); - } - - in_msg_body~skip_bits(size::query_id); ;; skip query_id - - process_actions(in_msg_body, false, true); - return (); - - } - - ;; Before signature checking we handle errors silently (return), after signature checking we throw exceptions. - - ;; Check to make sure that there are enough bits for reading before signature check - if (in_msg_body.slice_bits() < size::message_operation_prefix + size::wallet_id + size::valid_until + size::seqno + size::signature) { - return (); - } - process_signed_request(in_msg_body, false); -} - -;; ------------------------------------------------------------------------------------------------ -;; Get methods - -int is_signature_allowed() method_id { - return get_data().begin_parse() - .preload_int(size::bool); -} - -int seqno() method_id { - return get_data().begin_parse() - .skip_bits(size::bool) - .preload_uint(size::seqno); -} - -int get_subwallet_id() method_id { - return get_data().begin_parse() - .skip_bits(size::bool + size::seqno) - .preload_uint(size::wallet_id); -} - -int get_public_key() method_id { - return get_data().begin_parse() - .skip_bits(size::bool + size::seqno + size::wallet_id) - .preload_uint(size::public_key); -} - -;; Returns raw dictionary (or null if empty) where keys are address hashes. Workchains of extensions are same with wallet smart contract workchain. -cell get_extensions() method_id { - return get_data().begin_parse() - .skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key) - .preload_dict(); -} \ No newline at end of file diff --git a/devenv/src/main/resources/archetype-resources/src/test/resources/simple.fc b/devenv/src/main/resources/archetype-resources/src/test/resources/simple.fc index 69ac8e68..3a9d2ce7 100644 --- a/devenv/src/main/resources/archetype-resources/src/test/resources/simple.fc +++ b/devenv/src/main/resources/archetype-resources/src/test/resources/simple.fc @@ -2,11 +2,20 @@ #include "stdlib.fc"; -() recv_internal(int my_ton_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { +() recv_internal(int my_ton_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure inline { ~dump(40); + ~dump(my_ton_balance); + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + slice sender_address = cs~load_msg_addr(); + var x = in_msg_body~load_uint(32); + ~dump(x); + set_data(begin_cell().store_uint(x, 32).end_cell()); } -() recv_external(slice in_msg_body) impure { +() recv_external(slice in_msg_body) impure inline { ~dump(41); var msg_seqno = in_msg_body~load_uint(32); @@ -17,13 +26,10 @@ accept_message(); - ;; replay protection - set_data(begin_cell() - .store_uint(stored_seqno + 1, 32) - .end_cell()); + set_data(begin_cell().store_uint(stored_seqno + 1, 32).end_cell()); } -int get_id() method_id { +int seqno() method_id { ~dump(42); - return 42; + return get_data().begin_parse().preload_uint(32); } \ No newline at end of file diff --git a/emulator/pom.xml b/emulator/pom.xml index 82bcf49a..ce2dddba 100644 --- a/emulator/pom.xml +++ b/emulator/pom.xml @@ -73,7 +73,6 @@ ch.qos.logback logback-classic - test org.assertj diff --git a/emulator/src/main/java/org/ton/java/emulator/EmulateTransactionResult.java b/emulator/src/main/java/org/ton/java/emulator/EmulateTransactionResult.java index dc235d50..135cb1ee 100644 --- a/emulator/src/main/java/org/ton/java/emulator/EmulateTransactionResult.java +++ b/emulator/src/main/java/org/ton/java/emulator/EmulateTransactionResult.java @@ -43,4 +43,21 @@ public OutList getActions() { return OutList.builder().build(); } } + + public StateInit getNewStateInit() { + ShardAccount shardAccount; + if (StringUtils.isNotEmpty(shard_account)) { + shardAccount = + ShardAccount.deserialize(CellSlice.beginParse(Cell.fromBocBase64(shard_account))); + } else { + shardAccount = ShardAccount.builder().build(); + } + + AccountState accountState = shardAccount.getAccount().getAccountStorage().getAccountState(); + if (accountState instanceof AccountStateActive) { + return ((AccountStateActive) accountState).getStateInit(); + } + + return StateInit.builder().build(); + } } diff --git a/emulator/src/main/java/org/ton/java/emulator/tvm/TvmEmulator.java b/emulator/src/main/java/org/ton/java/emulator/tvm/TvmEmulator.java index 8f8dad1b..1840be50 100644 --- a/emulator/src/main/java/org/ton/java/emulator/tvm/TvmEmulator.java +++ b/emulator/src/main/java/org/ton/java/emulator/tvm/TvmEmulator.java @@ -9,7 +9,7 @@ import java.math.BigInteger; import java.util.Collections; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.ton.java.cell.CellSlice; import org.ton.java.tlb.types.VmStack; import org.ton.java.tlb.types.VmStackList; @@ -17,7 +17,7 @@ import org.ton.java.tlb.types.VmStackValueTinyInt; import org.ton.java.utils.Utils; -@Log +@Slf4j @Builder public class TvmEmulator { @@ -183,6 +183,21 @@ public GetMethodResult runGetMethod(int methodId) { return gson.fromJson(result, GetMethodResult.class); } + public GetMethodResult runGetMethod(String methodName) { + String result = + tvmEmulatorI.tvm_emulator_run_get_method( + tvmEmulator, + Utils.calculateMethodId(methodName), + VmStack.builder() + .depth(0) + .stack(VmStackList.builder().tos(Collections.emptyList()).build()) + .build() + .toCell() + .toBase64()); + Gson gson = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL).create(); + return gson.fromJson(result, GetMethodResult.class); + } + public BigInteger runGetSeqNo() { GetMethodResult methodResult = runGetMethod(Utils.calculateMethodId("seqno")); VmStack stack = methodResult.getStack(); diff --git a/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java b/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java index 1d11affd..b73f0c1f 100644 --- a/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java +++ b/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java @@ -10,7 +10,7 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.ton.java.cell.Cell; import org.ton.java.emulator.EmulateTransactionResult; @@ -21,7 +21,7 @@ * If not specified then tries to find emulator in system folder, more info here */ -@Log +@Slf4j @Builder public class TxEmulator { diff --git a/fift/pom.xml b/fift/pom.xml index de5bf061..6325ecd8 100644 --- a/fift/pom.xml +++ b/fift/pom.xml @@ -75,7 +75,6 @@ ch.qos.logback logback-classic - test org.assertj diff --git a/fift/src/main/java/org/ton/java/fift/Executor.java b/fift/src/main/java/org/ton/java/fift/Executor.java index d35d3383..8b528e1e 100644 --- a/fift/src/main/java/org/ton/java/fift/Executor.java +++ b/fift/src/main/java/org/ton/java/fift/Executor.java @@ -5,13 +5,13 @@ import java.nio.charset.Charset; import java.util.Arrays; import java.util.concurrent.TimeUnit; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.utils.Utils; -@Log +@Slf4j public class Executor { public static Pair execute( @@ -22,7 +22,7 @@ public static Pair execute( withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); try { - log.info("execute: " + String.join(" ", withBinaryCommand)); + log.info("execute: {}", String.join(" ", withBinaryCommand)); final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); @@ -39,7 +39,7 @@ public static Pair execute( if (p.exitValue() == 2 || p.exitValue() == 0) { return Pair.of(p, resultInput); } else { - log.info("exit value " + p.exitValue()); + log.info("exit value {}", p.exitValue()); log.info(resultInput); throw new Exception("Error running " + Arrays.toString(withBinaryCommand)); } @@ -57,7 +57,7 @@ public static Pair executeStdIn( String pathToBinary, String workDir, String stdin, String include) { try { - // log.info("execute: " + withBinaryCommand); + // log.info("execute: {}", withBinaryCommand); final ProcessBuilder pb; String cmd = ""; if (Utils.getOS() == Utils.OS.WINDOWS) { @@ -87,7 +87,7 @@ public static Pair executeStdIn( if (p.exitValue() == 2 || p.exitValue() == 0) { return Pair.of(p, resultInput); } else { - log.info("exit value " + p.exitValue()); + log.info("exit value {}", p.exitValue()); log.info(resultInput); throw new Exception("Error running " + cmd); } diff --git a/fift/src/main/java/org/ton/java/fift/FiftRunner.java b/fift/src/main/java/org/ton/java/fift/FiftRunner.java index d53e2241..52001d6d 100644 --- a/fift/src/main/java/org/ton/java/fift/FiftRunner.java +++ b/fift/src/main/java/org/ton/java/fift/FiftRunner.java @@ -5,14 +5,14 @@ import java.io.File; import java.util.concurrent.TimeUnit; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.utils.Utils; @Builder -@Log +@Slf4j public class FiftRunner { String fiftAsmLibraryPath; String fiftSmartcontLibraryPath; diff --git a/func/pom.xml b/func/pom.xml index 9a7761c1..e958385f 100644 --- a/func/pom.xml +++ b/func/pom.xml @@ -75,7 +75,6 @@ ch.qos.logback logback-classic - test org.assertj diff --git a/func/src/main/java/org/ton/java/func/Executor.java b/func/src/main/java/org/ton/java/func/Executor.java index 42f1d699..586bd5e3 100644 --- a/func/src/main/java/org/ton/java/func/Executor.java +++ b/func/src/main/java/org/ton/java/func/Executor.java @@ -1,53 +1,53 @@ package org.ton.java.func; -import lombok.extern.java.Log; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.tuple.Pair; - import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.tuple.Pair; -@Log +@Slf4j public class Executor { - public static Pair execute(String pathToBinary, String workDir, String... command) { + public static Pair execute( + String pathToBinary, String workDir, String... command) { - String[] withBinaryCommand = new String[]{pathToBinary}; + String[] withBinaryCommand = new String[] {pathToBinary}; - withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); + withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); - try { - log.info("execute: " + String.join(" ", withBinaryCommand)); + try { + log.info("execute: " + String.join(" ", withBinaryCommand)); - final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); + final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); - pb.directory(new File(workDir)); - Process p = pb.start(); + pb.directory(new File(workDir)); + Process p = pb.start(); - p.waitFor(1, TimeUnit.SECONDS); + p.waitFor(1, TimeUnit.SECONDS); - String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); - if (p.exitValue() == 2 || p.exitValue() == 0) { - return Pair.of(p, resultInput); - } else { - log.info("exit value " + p.exitValue()); - log.info(resultInput); - throw new Exception("Error running " + withBinaryCommand); - } + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + if (p.exitValue() == 2 || p.exitValue() == 0) { + return Pair.of(p, resultInput); + } else { + log.info("exit value {}", p.exitValue()); + log.info(resultInput); + throw new Exception("Error running " + withBinaryCommand); + } - } catch (final IOException e) { - log.info(e.getMessage()); - return null; - } catch (Exception e) { - log.info(e.getMessage()); - throw new RuntimeException(e); - } + } catch (final IOException e) { + log.info(e.getMessage()); + return null; + } catch (Exception e) { + log.info(e.getMessage()); + throw new RuntimeException(e); } + } } diff --git a/func/src/main/java/org/ton/java/func/FuncRunner.java b/func/src/main/java/org/ton/java/func/FuncRunner.java index c8a97b7a..7e7dd3f8 100644 --- a/func/src/main/java/org/ton/java/func/FuncRunner.java +++ b/func/src/main/java/org/ton/java/func/FuncRunner.java @@ -2,13 +2,13 @@ import java.util.concurrent.TimeUnit; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.utils.Utils; @Builder -@Log +@Slf4j public class FuncRunner { String funcExecutablePath; diff --git a/liteclient/pom.xml b/liteclient/pom.xml index dd288c62..319aec12 100644 --- a/liteclient/pom.xml +++ b/liteclient/pom.xml @@ -77,7 +77,6 @@ ch.qos.logback logback-classic - test org.apache.logging.log4j diff --git a/liteclient/src/main/java/org/ton/java/liteclient/LiteClient.java b/liteclient/src/main/java/org/ton/java/liteclient/LiteClient.java index 2bcff73d..f3972b99 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/LiteClient.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/LiteClient.java @@ -14,460 +14,504 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.liteclient.api.ResultLastBlock; import org.ton.java.liteclient.api.ResultListBlockTransactions; import org.ton.java.utils.Utils; -@Log +@Slf4j @Builder public class LiteClient { - private static final String LITE_CLIENT_EXE = "lite-client.exe"; - private static final String LITE_CLIENT = "lite-client"; + private static final String LITE_CLIENT_EXE = "lite-client.exe"; + private static final String LITE_CLIENT = "lite-client"; - private static LiteClient singleInstance = null; + private static LiteClient singleInstance = null; - private String pathToLiteClientBinary; + private String pathToLiteClientBinary; - /** - * if not specified and globalConfigAsString is null then integrated global-config.json is used; - *

- * if not specified and globalConfigAsString is filled then globalConfigAsString is used; - *

- * If not specified and testnet=true then integrated testnet-global.config.json is used; - */ - private String pathToGlobalConfig; - private int timeout; + /** + * if not specified and globalConfigAsString is null then integrated global-config.json is used; + * + *

if not specified and globalConfigAsString is filled then globalConfigAsString is used; + * + *

If not specified and testnet=true then integrated testnet-global.config.json is used; + */ + private String pathToGlobalConfig; - private String nodeName; + private int timeout; - /** - * Ignored if pathToGlobalConfig is not null. - */ - private boolean testnet; + private String nodeName; - public static class LiteClientBuilder { - } - - public static LiteClientBuilder builder() { - return new CustomLiteClientBuilder(); - } - - private static class CustomLiteClientBuilder extends LiteClientBuilder { - @Override - public LiteClient build() { - - try { - - if (isNull(super.pathToLiteClientBinary)) { - super.pathToLiteClientBinary = Utils.detectAbsolutePath("lite-client", false);; - } - - if (super.timeout == 0) { - super.timeout = 10; - } - - if (isNull(super.pathToGlobalConfig)) { - - InputStream config; - if (super.testnet) { - config = LiteClient.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); - File f = new File("testnet-global.config.json"); - FileUtils.copyInputStreamToFile(config, f); - super.pathToGlobalConfig = f.getAbsolutePath(); - } else { - config = LiteClient.class.getClassLoader().getResourceAsStream("global-config.json"); - File f = new File("global.config.json"); - FileUtils.copyInputStreamToFile(config, f); - super.pathToGlobalConfig = f.getAbsolutePath(); - } - - config.close(); - - } else { - if (!Files.exists(Paths.get(super.pathToGlobalConfig))) { - throw new RuntimeException("Global config is not found in path: " + super.pathToGlobalConfig); - } - } - - log.info(String.format("Java Lite-Client configuration:\n" + - "Location: %s\n" + - "Path to global config: %s\n" + - "Testnet: %s%n", - super.pathToLiteClientBinary, - super.pathToGlobalConfig, - super.testnet)); - - } catch (Exception e) { - throw new RuntimeException("Error creating lite-client instance: " + e.getMessage()); - } - return super.build(); - } - } - - public String getLastCommand() { - String command = "last"; - - String binaryPath = pathToLiteClientBinary; - - String[] withBinaryCommand; - withBinaryCommand = new String[]{binaryPath, "-t", "10", "-C", pathToGlobalConfig, "-c"}; - withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); - - return String.join(" ", withBinaryCommand); - } - - public String executeLast() { - String command = "last"; - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeLast error " + e.getMessage()); - return null; - } - } else { - return null; - } - } - - public long executeGetSeqno(String contractAddress) { - try { - return LiteClientParser.parseRunMethodSeqno(executeRunMethod(contractAddress, "seqno", "")); - } catch (Exception e) { - return -1L; - } - } - - public long executeGetSubWalletId(String contractAddress) { - try { - return LiteClientParser.parseRunMethodSeqno(executeRunMethod(contractAddress, "get_subwallet_id", "")); - } catch (Exception e) { - return -1L; - } - } - - /** - * @param seqno - is the pureBlockSeqno - * @return string result of lite-client output - */ - public String executeBySeqno(long wc, String shard, BigInteger seqno) throws Exception { - final String command = String.format("byseqno %d:%s %d", wc, shard, seqno); - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } - - /** - * @param resultLastBlock - full block id - * @param amountOfTransactions - if zero defaults to 100000 - * @return string result of lite-client output - */ - public String executeListblocktrans(final ResultLastBlock resultLastBlock, final long amountOfTransactions) { - final String command = String.format("listblocktrans %s %d", resultLastBlock.getFullBlockSeqno(), - (amountOfTransactions == 0) ? 100000 : amountOfTransactions); - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeListblocktrans error " + e.getMessage()); - return null; - } - } else { - return null; - } - } - - public String executeDumptrans(final ResultLastBlock resultLastBlock, final ResultListBlockTransactions tx) { - final String command = String.format("dumptrans %s %d:%s %d", resultLastBlock.getFullBlockSeqno(), resultLastBlock.getWc(), tx.getAccountAddress(), tx.getLt()); - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeDumptrans error " + e.getMessage()); - return null; - } - } else { - return null; - } - } - - public String executeDumptrans(String tx) { - final String command = String.format("dumptrans %s", tx); - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeDumptrans error " + e.getMessage()); - return null; - } - } else { - return null; - } - } - - public String executeDumpblock(final ResultLastBlock resultLastBlock) { - final String command = String.format("dumpblock %s", resultLastBlock.getFullBlockSeqno()); - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeDumpblock error " + e.getMessage()); - return null; - } - } else { - return null; - } - } - - public String executeDumpblock(String fullBlockSeqno) { - final String command = String.format("dumpblock %s", fullBlockSeqno); - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeDumpblock error " + e.getMessage()); - return null; - } - } else { - return null; - } - } - - public String executeAllshards(final ResultLastBlock resultLastBlock) throws Exception { - final String command = "allshards " + resultLastBlock.getFullBlockSeqno(); - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } - - public String executeGetAccount(String address) { - final String command = "getaccount " + address; - Pair> result = execute(command); - if (nonNull(result)) { - try { - return result.getRight().get(); - } catch (Exception e) { - log.info("executeGetAccount error " + e.getMessage()); - return null; - } - } else { - return null; - } - } + /** Ignored if pathToGlobalConfig is not null. */ + private boolean testnet; - public String executeRunMethod(String address, String methodId, String params) throws Exception { - final String command = String.format("runmethod %s %s %s", address, methodId, params); - return execute(command).getRight().get(); - } + public static class LiteClientBuilder {} - /** - * @param address base64 or raw - * @param blockId in format (-1,8000000000000000,20301335):root-hash:file-hash - * @param methodId method name, e.g. seqno - * @param params space separated params - * @return lite-client output - */ - public String executeRunMethod(String address, String blockId, String methodId, String params) throws Exception { - final String command = String.format("runmethod %s %s %s %s", address, blockId, methodId, params); - log.info(command); - return execute(command).getRight().get(); - } + public static LiteClientBuilder builder() { + return new CustomLiteClientBuilder(); + } - /** - * @param address base64 or raw - * @param blockId ResultLastBlock - * @param methodId method name, e.g. seqno - * @param params space separated params - * @return lite-client output - */ - public String executeRunMethod(String address, ResultLastBlock blockId, String methodId, String params) throws Exception { - final String command = String.format("runmethod %s %s %s %s", address, blockId.getFullBlockSeqno(), methodId, params); - log.info(command); - return execute(command).getRight().get(); - } + private static class CustomLiteClientBuilder extends LiteClientBuilder { + @Override + public LiteClient build() { - public String executeSendfile(String absolutePathFile) throws Exception { - final String command = "sendfile " + absolutePathFile; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } + try { - public String executeGetElections() throws Exception { - // - final String command = "getconfig 15"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } - - public String executeGetConfigSmcAddress() throws Exception { - final String command = "getconfig 0"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } - - public String executeGetElectorSmcAddress() throws Exception { - final String command = "getconfig 1"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; + if (StringUtils.isEmpty(super.pathToLiteClientBinary)) { + log.info("checking if lite-client is installed..."); + String errorMsg = + "Make sure you have fift installed. See https://github.com/ton-blockchain/packages for instructions.\nYou can also specify full path via SmartContractCompiler.fiftExecutablePath()."; + try { + ProcessBuilder pb = new ProcessBuilder("lite-client", "-h").redirectErrorStream(true); + Process p = pb.start(); + p.waitFor(1, TimeUnit.SECONDS); + // if (p.exitValue() != 2) { + // throw new Error("Cannot execute lite-client command.\n" + errorMsg); + // } + super.pathToLiteClientBinary = Utils.detectAbsolutePath("lite-client", false); + log.info("lite-client found at " + super.pathToLiteClientBinary); + + } catch (Exception e) { + throw new Error("Cannot execute simple lite-client command.\n" + errorMsg); + } } - } - public String executeGetMinterSmcAddress() throws Exception { - final String command = "getconfig 2"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; + if (super.timeout == 0) { + super.timeout = 10; } - } - - // start of the validation cycle - public long executeGetActiveElectionId(String electorAddr) throws Exception { - return LiteClientParser.parseRunMethodSeqno(executeRunMethod(electorAddr, "active_election_id", "")); - } - - public String executeGetParticipantList(String electorAddr) throws Exception { - // parseRunMethodParticipantList - return executeRunMethod(electorAddr, "participant_list", ""); - } - public String executeComputeReturnedStake(String electorAddr, String validatorWalletAddr) throws Exception { - // parseRunMethodComputeReturnedStake - //final String command = String.format("runmethod %s %s 0x%s", electorAddr, "compute_returned_stake", validatorWalletAddr); - //log.info(command); - return executeRunMethod(electorAddr, "compute_returned_stake", "0x" + validatorWalletAddr.trim().toLowerCase()); - } + if (isNull(super.pathToGlobalConfig)) { - public String executeGetMinMaxStake() throws Exception { - final String command = "getconfig 17"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } + InputStream config; + if (super.testnet) { + config = + LiteClient.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); + File f = new File("testnet-global.config.json"); + FileUtils.copyInputStreamToFile(config, f); + super.pathToGlobalConfig = f.getAbsolutePath(); + } else { + config = LiteClient.class.getClassLoader().getResourceAsStream("global-config.json"); + File f = new File("global.config.json"); + FileUtils.copyInputStreamToFile(config, f); + super.pathToGlobalConfig = f.getAbsolutePath(); + } - public String executeGetPreviousValidators() throws Exception { - final String command = "getconfig 32"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } + config.close(); - public String executeGetCurrentValidators() throws Exception { - final String command = "getconfig 34"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); } else { - return null; + if (!Files.exists(Paths.get(super.pathToGlobalConfig))) { + throw new RuntimeException( + "Global config is not found in path: " + super.pathToGlobalConfig); + } } - } - public String executeGetNextValidators() throws Exception { - final String command = "getconfig 36"; - Pair> result = execute(command); - if (nonNull(result)) { - return result.getRight().get(); - } else { - return null; - } - } - - public List getShardsFromBlock(ResultLastBlock lastBlock) { - try { - List foundShardsInBlock = LiteClientParser.parseAllShards(executeAllshards(lastBlock)); - log.info("found " + foundShardsInBlock.size() + " shards in block " + foundShardsInBlock); - return foundShardsInBlock; - } catch (Exception e) { - log.info("Error retrieving shards from the block " + e.getMessage()); - return null; - } - } - - public Pair> execute(String... command) { - - String binaryPath = pathToLiteClientBinary; - String[] withBinaryCommand; - withBinaryCommand = new String[]{binaryPath, "-t", String.valueOf(timeout), "-C", pathToGlobalConfig, "-c"}; - - //String[] withBinaryCommand = {binaryPath, "-C", forked ? node.getNodeForkedGlobalConfigLocation() : node.getNodeGlobalConfigLocation(), "-c"}; - withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); - - try { - log.info("execute: " + String.join(" ", withBinaryCommand)); - - ExecutorService executorService = Executors.newSingleThreadExecutor(); - - final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); - - pb.directory(new File(new File(binaryPath).getParent())); - Process p = pb.start(); - - Future future = executorService.submit(() -> { + log.info( + String.format( + "Java Lite-Client configuration:\n" + + "Location: %s\n" + + "Path to global config: %s\n" + + "Testnet: %s%n", + super.pathToLiteClientBinary, super.pathToGlobalConfig, super.testnet)); + + } catch (Exception e) { + throw new RuntimeException("Error creating lite-client instance: " + e.getMessage()); + } + return super.build(); + } + } + + public String getLastCommand() { + String command = "last"; + + String binaryPath = pathToLiteClientBinary; + + String[] withBinaryCommand; + withBinaryCommand = new String[] {binaryPath, "-t", "10", "-C", pathToGlobalConfig, "-c"}; + withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); + + return String.join(" ", withBinaryCommand); + } + + public String executeLast() { + String command = "last"; + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeLast error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public long executeGetSeqno(String contractAddress) { + try { + return LiteClientParser.parseRunMethodSeqno(executeRunMethod(contractAddress, "seqno", "")); + } catch (Exception e) { + return -1L; + } + } + + public long executeGetSubWalletId(String contractAddress) { + try { + return LiteClientParser.parseRunMethodSeqno( + executeRunMethod(contractAddress, "get_subwallet_id", "")); + } catch (Exception e) { + return -1L; + } + } + + /** + * @param seqno - is the pureBlockSeqno + * @return string result of lite-client output + */ + public String executeBySeqno(long wc, String shard, BigInteger seqno) throws Exception { + final String command = String.format("byseqno %d:%s %d", wc, shard, seqno); + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + /** + * @param resultLastBlock - full block id + * @param amountOfTransactions - if zero defaults to 100000 + * @return string result of lite-client output + */ + public String executeListblocktrans( + final ResultLastBlock resultLastBlock, final long amountOfTransactions) { + final String command = + String.format( + "listblocktrans %s %d", + resultLastBlock.getFullBlockSeqno(), + (amountOfTransactions == 0) ? 100000 : amountOfTransactions); + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeListblocktrans error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public String executeDumptrans( + final ResultLastBlock resultLastBlock, final ResultListBlockTransactions tx) { + final String command = + String.format( + "dumptrans %s %d:%s %d", + resultLastBlock.getFullBlockSeqno(), + resultLastBlock.getWc(), + tx.getAccountAddress(), + tx.getLt()); + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeDumptrans error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public String executeDumptrans(String tx) { + final String command = String.format("dumptrans %s", tx); + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeDumptrans error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public String executeDumpblock(final ResultLastBlock resultLastBlock) { + final String command = String.format("dumpblock %s", resultLastBlock.getFullBlockSeqno()); + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeDumpblock error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public String executeDumpblock(String fullBlockSeqno) { + final String command = String.format("dumpblock %s", fullBlockSeqno); + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeDumpblock error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public String executeAllshards(final ResultLastBlock resultLastBlock) throws Exception { + final String command = "allshards " + resultLastBlock.getFullBlockSeqno(); + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetAccount(String address) { + final String command = "getaccount " + address; + Pair> result = execute(command); + if (nonNull(result)) { + try { + return result.getRight().get(); + } catch (Exception e) { + log.info("executeGetAccount error " + e.getMessage()); + return null; + } + } else { + return null; + } + } + + public String executeRunMethod(String address, String methodId, String params) throws Exception { + final String command = String.format("runmethod %s %s %s", address, methodId, params); + return execute(command).getRight().get(); + } + + /** + * @param address base64 or raw + * @param blockId in format (-1,8000000000000000,20301335):root-hash:file-hash + * @param methodId method name, e.g. seqno + * @param params space separated params + * @return lite-client output + */ + public String executeRunMethod(String address, String blockId, String methodId, String params) + throws Exception { + final String command = + String.format("runmethod %s %s %s %s", address, blockId, methodId, params); + log.info(command); + return execute(command).getRight().get(); + } + + /** + * @param address base64 or raw + * @param blockId ResultLastBlock + * @param methodId method name, e.g. seqno + * @param params space separated params + * @return lite-client output + */ + public String executeRunMethod( + String address, ResultLastBlock blockId, String methodId, String params) throws Exception { + final String command = + String.format( + "runmethod %s %s %s %s", address, blockId.getFullBlockSeqno(), methodId, params); + log.info(command); + return execute(command).getRight().get(); + } + + public String executeSendfile(String absolutePathFile) throws Exception { + final String command = "sendfile " + absolutePathFile; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetElections() throws Exception { + // + final String command = "getconfig 15"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetConfigSmcAddress() throws Exception { + final String command = "getconfig 0"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetElectorSmcAddress() throws Exception { + final String command = "getconfig 1"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetMinterSmcAddress() throws Exception { + final String command = "getconfig 2"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + // start of the validation cycle + public long executeGetActiveElectionId(String electorAddr) throws Exception { + return LiteClientParser.parseRunMethodSeqno( + executeRunMethod(electorAddr, "active_election_id", "")); + } + + public String executeGetParticipantList(String electorAddr) throws Exception { + // parseRunMethodParticipantList + return executeRunMethod(electorAddr, "participant_list", ""); + } + + public String executeComputeReturnedStake(String electorAddr, String validatorWalletAddr) + throws Exception { + // parseRunMethodComputeReturnedStake + // final String command = String.format("runmethod %s %s 0x%s", electorAddr, + // "compute_returned_stake", validatorWalletAddr); + // log.info(command); + return executeRunMethod( + electorAddr, "compute_returned_stake", "0x" + validatorWalletAddr.trim().toLowerCase()); + } + + public String executeGetMinMaxStake() throws Exception { + final String command = "getconfig 17"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetPreviousValidators() throws Exception { + final String command = "getconfig 32"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetCurrentValidators() throws Exception { + final String command = "getconfig 34"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public String executeGetNextValidators() throws Exception { + final String command = "getconfig 36"; + Pair> result = execute(command); + if (nonNull(result)) { + return result.getRight().get(); + } else { + return null; + } + } + + public List getShardsFromBlock(ResultLastBlock lastBlock) { + try { + List foundShardsInBlock = + LiteClientParser.parseAllShards(executeAllshards(lastBlock)); + log.info("found " + foundShardsInBlock.size() + " shards in block " + foundShardsInBlock); + return foundShardsInBlock; + } catch (Exception e) { + log.info("Error retrieving shards from the block " + e.getMessage()); + return null; + } + } + + public Pair> execute(String... command) { + + String binaryPath = pathToLiteClientBinary; + String[] withBinaryCommand; + withBinaryCommand = + new String[] {binaryPath, "-t", String.valueOf(timeout), "-C", pathToGlobalConfig, "-c"}; + + // String[] withBinaryCommand = {binaryPath, "-C", forked ? + // node.getNodeForkedGlobalConfigLocation() : node.getNodeGlobalConfigLocation(), "-c"}; + withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); + + try { + log.info("execute: " + String.join(" ", withBinaryCommand)); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); + + pb.directory(new File(new File(binaryPath).getParent())); + Process p = pb.start(); + p.waitFor(1, TimeUnit.SECONDS); + Future future = + executorService.submit( + () -> { try { - Thread.currentThread().setName("lite-client-" + nodeName); + Thread.currentThread().setName("lite-client-" + nodeName); - String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + String resultInput = + IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); - return resultInput; + return resultInput; } catch (IOException e) { - log.info(e.getMessage()); - return null; + log.info(e.getMessage()); + return null; } - }); + }); - executorService.shutdown(); + executorService.shutdown(); - return Pair.of(p, future); + return Pair.of(p, future); - } catch (final IOException e) { - log.info(e.getMessage()); - return null; - } + } catch (final IOException | InterruptedException e) { + log.info(e.getMessage()); + return null; } + } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/LiteClientParser.java b/liteclient/src/main/java/org/ton/java/liteclient/LiteClientParser.java index 6339c814..1072912e 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/LiteClientParser.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/LiteClientParser.java @@ -1,1716 +1,1718 @@ package org.ton.java.liteclient; -import lombok.extern.java.Log; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.ton.java.liteclient.api.*; -import org.ton.java.liteclient.api.block.Currency; import org.ton.java.liteclient.api.block.*; +import org.ton.java.liteclient.api.block.Currency; import org.ton.java.liteclient.api.config.Validator; import org.ton.java.liteclient.api.config.Validators; import org.ton.java.liteclient.exception.IncompleteDump; import org.ton.java.liteclient.exception.ParsingError; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.stream.Collectors; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - -@Log +@Slf4j public class LiteClientParser { - public static final String EOL = "\n"; - private static final String EOLWIN = "\r\n"; - private static final byte NOT_EXISTS_42 = -42; - private static final String VALUE_COLON = "value:"; - public static final String SPACE = " "; - public static final String OPEN = "("; - public static final String CLOSE = ")"; - private static final String OPEN_CURLY = "{"; - private static final String TRANSACTION_TAG = "transaction #"; - private static final String WORKCHAIN_ID_COLON = "workchain_id:"; - private static final String ADDRESS_COLON = "address:"; - private static final String BOUNCE_COLON = "bounce:"; - private static final String SUCCESS_COLON = "success:"; - private static final String SEQ_NO_COLON = "seq_no:"; - private static final String END_LT_COLON = "end_lt:"; - private static final String START_LT_COLON = "start_lt:"; - - private LiteClientParser() { - } - - public static ResultLastBlock parseLast(String stdout) { - - if (StringUtils.isEmpty(stdout)) { - log.info("parseLast, stdout is empty: " + stdout); - return null; - } - - if (StringUtils.indexOf(stdout, "adnl query timeout") != -1) { - log.info("Blockchain node is not ready"); - return null; - } - - if (StringUtils.indexOf(stdout, "server appears to be out of sync") != -1) { - log.info("Blockchain node is out of sync"); - } - - try { - - String last = stdout.replace(EOL, SPACE); - Long createdAt = Long.parseLong(sb(last, "created at ", OPEN).trim()); - String fullBlockSeqno = sb(last, "last masterchain block is ", SPACE).trim(); - String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; - String rootHashId = sb(fullBlockSeqno, ":", ":"); - String fileHashId = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); - String shard = sb(shortBlockSeqno, ",", ","); - BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); - Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); - Long secondsAgo = -1L; - if (last.contains("seconds ago")) { - String createdAtStr = sb(last, "created at ", "seconds ago"); - secondsAgo = Long.parseLong(sb(createdAtStr, OPEN, SPACE).trim()); - } - - return ResultLastBlock.builder() - .createdAt(createdAt) - .seqno(pureBlockSeqno) - .rootHash(rootHashId) - .fileHash(fileHashId) - .wc(wc) - .shard(shard) - .syncedSecondsAgo(secondsAgo) - .build(); - - } catch (Exception e) { - log.info("Error parsing lite-client's last command! Output: " + stdout); - return null; - } + public static final String EOL = "\n"; + private static final String EOLWIN = "\r\n"; + private static final byte NOT_EXISTS_42 = -42; + private static final String VALUE_COLON = "value:"; + public static final String SPACE = " "; + public static final String OPEN = "("; + public static final String CLOSE = ")"; + private static final String OPEN_CURLY = "{"; + private static final String TRANSACTION_TAG = "transaction #"; + private static final String WORKCHAIN_ID_COLON = "workchain_id:"; + private static final String ADDRESS_COLON = "address:"; + private static final String BOUNCE_COLON = "bounce:"; + private static final String SUCCESS_COLON = "success:"; + private static final String SEQ_NO_COLON = "seq_no:"; + private static final String END_LT_COLON = "end_lt:"; + private static final String START_LT_COLON = "start_lt:"; + + private LiteClientParser() {} + + public static ResultLastBlock parseLast(String stdout) { + + if (StringUtils.isEmpty(stdout)) { + log.info("parseLast, stdout is empty: {}", stdout); + return null; } - public static ResultLastBlock parseCreateHardFork(String stdout) { - - if (StringUtils.isEmpty(stdout)) { - log.info("parseCreateHardfork, stdout is empty: " + stdout); - return null; - } - - try { - - String last = stdout.replace(EOL, SPACE); - String fullBlockSeqno = last.substring(last.indexOf("saved to disk") + 13).trim(); - String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; - String rootHashId = sb(fullBlockSeqno, ":", ":"); - String fileHashId = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); - String shard = sb(shortBlockSeqno, ",", ","); - BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); - Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); - - return ResultLastBlock.builder() - .seqno(pureBlockSeqno) - .rootHash(rootHashId) - .fileHash(fileHashId) - .wc(wc) - .shard(shard) - .build(); - - } catch (Exception e) { - log.info("Error parsing create-hardfork's command! Output: " + stdout + ", error: " + e.getMessage()); - return null; - } + if (StringUtils.indexOf(stdout, "adnl query timeout") != -1) { + log.info("Blockchain node is not ready"); + return null; } - public static ResultLastBlock parseBySeqno(String stdout) throws IncompleteDump, ParsingError { - - if (StringUtils.isEmpty(stdout) || stdout.contains("seqno not in db") || stdout.contains("block not found")) - throw new IncompleteDump("parseBySeqno: block is missing"); - - if (!stdout.contains("global_id")) - throw new IncompleteDump("parseBySeqno: incomplete dump or block missing"); - try { - - String last = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - - String fullBlockSeqno = sb(last, "obtained block header for ", " from server"); - String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; - String rootHashId = sb(fullBlockSeqno, ":", ":"); - String fileHashId = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); - String createdAt = sb(last, "@", "lt").trim(); - - String shard = sb(shortBlockSeqno, ",", ","); - BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); - Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); - - return ResultLastBlock.builder() - .seqno(pureBlockSeqno) - .rootHash(rootHashId) - .fileHash(fileHashId) - .wc(wc) - .shard(shard) - .createdAt(Long.valueOf(createdAt)) - .build(); - - } catch (Exception e) { - throw new ParsingError("parseBySeqno: parsing error", e); - } + if (StringUtils.indexOf(stdout, "server appears to be out of sync") != -1) { + log.info("Blockchain node is out of sync"); } - public static List parseListBlockTrans(String stdout) { - - if (StringUtils.isEmpty(stdout) || !stdout.contains(TRANSACTION_TAG)) - return Collections.emptyList(); - - String onlyTransactions = stdout.substring(stdout.indexOf(TRANSACTION_TAG)); - - String[] lines = onlyTransactions.split("\\r?\\n"); - - List txs = new ArrayList<>(); - - for (String line : lines) { - if (line.contains(TRANSACTION_TAG)) { - BigInteger txSeqno = new BigInteger(sb(line, TRANSACTION_TAG, ":")); - String txAccountAddress = sb(line, "account ", " lt").toUpperCase(); - BigInteger txLogicalTime = new BigInteger(sb(line, "lt ", " hash")); - String txHash = line.substring(line.indexOf("hash ") + 5); - txs.add(ResultListBlockTransactions.builder() - .txSeqno(txSeqno) - .accountAddress(txAccountAddress) - .lt(txLogicalTime) - .hash(txHash) - .build()); - } - } - return txs; + try { + + String last = stdout.replace(EOL, SPACE); + Long createdAt = Long.parseLong(sb(last, "created at ", OPEN).trim()); + String fullBlockSeqno = sb(last, "last masterchain block is ", SPACE).trim(); + String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; + String rootHashId = sb(fullBlockSeqno, ":", ":"); + String fileHashId = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); + String shard = sb(shortBlockSeqno, ",", ","); + BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); + Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); + Long secondsAgo = -1L; + if (last.contains("seconds ago")) { + String createdAtStr = sb(last, "created at ", "seconds ago"); + secondsAgo = Long.parseLong(sb(createdAtStr, OPEN, SPACE).trim()); + } + + return ResultLastBlock.builder() + .createdAt(createdAt) + .seqno(pureBlockSeqno) + .rootHash(rootHashId) + .fileHash(fileHashId) + .wc(wc) + .shard(shard) + .syncedSecondsAgo(secondsAgo) + .build(); + + } catch (Exception e) { + log.info("Error parsing lite-client's last command! Output: {}", stdout); + return null; } + } - public static Transaction parseDumpTrans(String stdout, boolean includeMessageBody) { - if (StringUtils.isEmpty(stdout)) { - return null; - } - String blockdump = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - return parseTransaction(blockdump, includeMessageBody); - } + public static ResultLastBlock parseCreateHardFork(String stdout) { - // config address - public static ResultConfig0 parseConfig0(String stdout) { - return ResultConfig0.builder() - .configSmcAddr("-1:" + sb(stdout, "config_addr:x", CLOSE)) - .build(); + if (StringUtils.isEmpty(stdout)) { + log.info("parseCreateHardfork, stdout is empty: {}", stdout); + return null; } - // elector address - public static ResultConfig1 parseConfig1(String stdout) { - return ResultConfig1.builder() - .electorSmcAddress("-1:" + sb(stdout, "elector_addr:x", CLOSE)) - .build(); + try { + + String last = stdout.replace(EOL, SPACE); + String fullBlockSeqno = last.substring(last.indexOf("saved to disk") + 13).trim(); + String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; + String rootHashId = sb(fullBlockSeqno, ":", ":"); + String fileHashId = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); + String shard = sb(shortBlockSeqno, ",", ","); + BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); + Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); + + return ResultLastBlock.builder() + .seqno(pureBlockSeqno) + .rootHash(rootHashId) + .fileHash(fileHashId) + .wc(wc) + .shard(shard) + .build(); + + } catch (Exception e) { + log.info( + "Error parsing create-hardfork's command! Output: " + + stdout + + ", error: " + + e.getMessage()); + return null; } - - // minter address - public static ResultConfig2 parseConfig2(String stdout) { - return ResultConfig2.builder() - .minterSmcAddress("-1:" + sb(stdout, "minter_addr:x", CLOSE)) - .build(); + } + + public static ResultLastBlock parseBySeqno(String stdout) throws IncompleteDump, ParsingError { + + if (StringUtils.isEmpty(stdout) + || stdout.contains("seqno not in db") + || stdout.contains("block not found")) + throw new IncompleteDump("parseBySeqno: block is missing"); + + if (!stdout.contains("global_id")) + throw new IncompleteDump("parseBySeqno: incomplete dump or block missing"); + try { + + String last = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + String fullBlockSeqno = sb(last, "obtained block header for ", " from server"); + String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; + String rootHashId = sb(fullBlockSeqno, ":", ":"); + String fileHashId = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); + String createdAt = sb(last, "@", "lt").trim(); + + String shard = sb(shortBlockSeqno, ",", ","); + BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); + Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); + + return ResultLastBlock.builder() + .seqno(pureBlockSeqno) + .rootHash(rootHashId) + .fileHash(fileHashId) + .wc(wc) + .shard(shard) + .createdAt(Long.valueOf(createdAt)) + .build(); + + } catch (Exception e) { + throw new ParsingError("parseBySeqno: parsing error", e); } + } - public static ResultConfig12 parseConfig12(String stdout) { + public static List parseListBlockTrans(String stdout) { - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + if (StringUtils.isEmpty(stdout) || !stdout.contains(TRANSACTION_TAG)) + return Collections.emptyList(); - return ResultConfig12.builder() - .enabledSince(Long.parseLong(sb(stdout, "workchain enabled_since:", SPACE))) - .actualMinSplit(Long.parseLong(sb(stdout, "actual_min_split:", SPACE))) - .minSplit(Long.parseLong(sb(stdout, "min_split:", SPACE))) - .maxSplit(Long.parseLong(sb(stdout, "max_split:", SPACE))) - .basic(Long.parseLong(sb(stdout, "basic:", SPACE))) - .active(Long.parseLong(sb(stdout, "active:", SPACE))) - .acceptMsg(Long.parseLong(sb(stdout, "accept_msgs:", SPACE))) - .flags(Long.parseLong(sb(stdout, "flags:", SPACE))) - .rootHash(sb(stdout, "zerostate_root_hash:x", SPACE)) - .fileHash(sb(stdout, "zerostate_file_hash:x", SPACE)) - .version(Long.parseLong(sb(stdout, "version:", SPACE))) - .build(); - } + String onlyTransactions = stdout.substring(stdout.indexOf(TRANSACTION_TAG)); - public static ResultConfig15 parseConfig15(String stdout) { + String[] lines = onlyTransactions.split("\\r?\\n"); - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + List txs = new ArrayList<>(); - // validators_elected_for:4000 elections_start_before:2000 elections_end_before:500 stake_held_for:1000 - return ResultConfig15.builder() - .validatorsElectedFor(Long.parseLong(sb(stdout, "validators_elected_for:", SPACE))) - .electionsStartBefore(Long.parseLong(sb(stdout, "elections_start_before:", SPACE))) - .electionsEndBefore(Long.parseLong(sb(stdout, "elections_end_before:", SPACE))) - .stakeHeldFor(Long.parseLong(sb(stdout, "stake_held_for:", ")"))) - .build(); + for (String line : lines) { + if (line.contains(TRANSACTION_TAG)) { + BigInteger txSeqno = new BigInteger(sb(line, TRANSACTION_TAG, ":")); + String txAccountAddress = sb(line, "account ", " lt").toUpperCase(); + BigInteger txLogicalTime = new BigInteger(sb(line, "lt ", " hash")); + String txHash = line.substring(line.indexOf("hash ") + 5); + txs.add( + ResultListBlockTransactions.builder() + .txSeqno(txSeqno) + .accountAddress(txAccountAddress) + .lt(txLogicalTime) + .hash(txHash) + .build()); + } } + return txs; + } - public static ResultConfig17 parseConfig17(String stdout) { - - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - - String minStake = sbb(stdout, "min_stake:"); - String maxStake = sbb(stdout, "max_stake:"); - String minTotalStake = sbb(stdout, "min_total_stake:"); - - return ResultConfig17.builder() - .minStake(parseBigIntegerBracket(minStake, "value:")) - .maxStake(parseBigIntegerBracket(maxStake, "value:")) - .minTotalStake(parseBigIntegerBracket(minTotalStake, "value:")) - .maxStakeFactor(parseBigIntegerBracket(stdout, "max_stake_factor:")) - .build(); + public static Transaction parseDumpTrans(String stdout, boolean includeMessageBody) { + if (StringUtils.isEmpty(stdout)) { + return null; + } + String blockdump = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + return parseTransaction(blockdump, includeMessageBody); + } + + // config address + public static ResultConfig0 parseConfig0(String stdout) { + return ResultConfig0.builder() + .configSmcAddr("-1:" + sb(stdout, "config_addr:x", CLOSE)) + .build(); + } + + // elector address + public static ResultConfig1 parseConfig1(String stdout) { + return ResultConfig1.builder() + .electorSmcAddress("-1:" + sb(stdout, "elector_addr:x", CLOSE)) + .build(); + } + + // minter address + public static ResultConfig2 parseConfig2(String stdout) { + return ResultConfig2.builder() + .minterSmcAddress("-1:" + sb(stdout, "minter_addr:x", CLOSE)) + .build(); + } + + public static ResultConfig12 parseConfig12(String stdout) { + + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + return ResultConfig12.builder() + .enabledSince(Long.parseLong(sb(stdout, "workchain enabled_since:", SPACE))) + .actualMinSplit(Long.parseLong(sb(stdout, "actual_min_split:", SPACE))) + .minSplit(Long.parseLong(sb(stdout, "min_split:", SPACE))) + .maxSplit(Long.parseLong(sb(stdout, "max_split:", SPACE))) + .basic(Long.parseLong(sb(stdout, "basic:", SPACE))) + .active(Long.parseLong(sb(stdout, "active:", SPACE))) + .acceptMsg(Long.parseLong(sb(stdout, "accept_msgs:", SPACE))) + .flags(Long.parseLong(sb(stdout, "flags:", SPACE))) + .rootHash(sb(stdout, "zerostate_root_hash:x", SPACE)) + .fileHash(sb(stdout, "zerostate_file_hash:x", SPACE)) + .version(Long.parseLong(sb(stdout, "version:", SPACE))) + .build(); + } + + public static ResultConfig15 parseConfig15(String stdout) { + + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + // validators_elected_for:4000 elections_start_before:2000 elections_end_before:500 + // stake_held_for:1000 + return ResultConfig15.builder() + .validatorsElectedFor(Long.parseLong(sb(stdout, "validators_elected_for:", SPACE))) + .electionsStartBefore(Long.parseLong(sb(stdout, "elections_start_before:", SPACE))) + .electionsEndBefore(Long.parseLong(sb(stdout, "elections_end_before:", SPACE))) + .stakeHeldFor(Long.parseLong(sb(stdout, "stake_held_for:", ")"))) + .build(); + } + + public static ResultConfig17 parseConfig17(String stdout) { + + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + String minStake = sbb(stdout, "min_stake:"); + String maxStake = sbb(stdout, "max_stake:"); + String minTotalStake = sbb(stdout, "min_total_stake:"); + + return ResultConfig17.builder() + .minStake(parseBigIntegerBracket(minStake, "value:")) + .maxStake(parseBigIntegerBracket(maxStake, "value:")) + .minTotalStake(parseBigIntegerBracket(minTotalStake, "value:")) + .maxStakeFactor(parseBigIntegerBracket(stdout, "max_stake_factor:")) + .build(); + } + + private static List parseConfigValidators(String stdout) { + List validators = new ArrayList<>(); + + List unparsedLeafs = findStringBlocks(stdout, "node:(hmn_leaf"); + + for (String leaf : unparsedLeafs) { + + String pubKey = sb(leaf, "pubkey:x", CLOSE); + BigInteger weight; + String adnlAddress; + + if (leaf.contains("adnl_addr:x")) { + weight = new BigInteger(sb(leaf, "weight:", SPACE)); + adnlAddress = sb(leaf, "adnl_addr:x", CLOSE); + } else { + weight = new BigInteger(sb(leaf, "weight:", CLOSE)); + adnlAddress = null; + } + validators.add( + Validator.builder().publicKey(pubKey).adnlAddress(adnlAddress).weight(weight).build()); } - private static List parseConfigValidators(String stdout) { - List validators = new ArrayList<>(); + return validators; + } - List unparsedLeafs = findStringBlocks(stdout, "node:(hmn_leaf"); + /** current validators */ + public static ResultConfig34 parseConfig34(String stdout) { - for (String leaf : unparsedLeafs) { + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - String pubKey = sb(leaf, "pubkey:x", CLOSE); - BigInteger weight; - String adnlAddress; + List validators = new ArrayList<>(); - if (leaf.contains("adnl_addr:x")) { - weight = new BigInteger(sb(leaf, "weight:", SPACE)); - adnlAddress = sb(leaf, "adnl_addr:x", CLOSE); - } else { - weight = new BigInteger(sb(leaf, "weight:", CLOSE)); - adnlAddress = null; - } - validators.add(Validator.builder() - .publicKey(pubKey) - .adnlAddress(adnlAddress) - .weight(weight) - .build()); - } - - return validators; + if (stdout.contains("ConfigParam(34) = (null)")) { + return ResultConfig34.builder() + .validators( + Validators.builder() + .since(0L) + .until(0L) + .total(0L) + .main(0L) + .totalWeight(BigInteger.ZERO) + .validators(validators) + .build()) + .build(); } - /** - * current validators - */ - public static ResultConfig34 parseConfig34(String stdout) { - - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - - List validators = new ArrayList<>(); - - if (stdout.contains("ConfigParam(34) = (null)")) { - return ResultConfig34.builder() - .validators(Validators.builder() - .since(0L) - .until(0L) - .total(0L) - .main(0L) - .totalWeight(BigInteger.ZERO) - .validators(validators) - .build()) - .build(); - } - - validators = parseConfigValidators(stdout); - - return ResultConfig34.builder() - .validators(Validators.builder() - .since(Long.parseLong(sb(stdout, "utime_since:", SPACE))) - .until(Long.parseLong(sb(stdout, "utime_until:", SPACE))) - .total(Long.parseLong(sb(stdout, "total:", SPACE))) - .main(Long.parseLong(sb(stdout, "main:", SPACE))) - .totalWeight(new BigInteger(sb(stdout, "total_weight:", SPACE))) - .validators(validators) - .build()) - .build(); - } - - /** - * next validators - */ - public static ResultConfig36 parseConfig36(String stdout) { - - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - - List validators = new ArrayList<>(); - - if (stdout.contains("ConfigParam(36) = (null)")) { - return ResultConfig36.builder() - .validators(Validators.builder() - .since(0L) - .until(0L) - .total(0L) - .main(0L) - .totalWeight(BigInteger.ZERO) - .validators(validators) - .build()) - .build(); - } + validators = parseConfigValidators(stdout); + + return ResultConfig34.builder() + .validators( + Validators.builder() + .since(Long.parseLong(sb(stdout, "utime_since:", SPACE))) + .until(Long.parseLong(sb(stdout, "utime_until:", SPACE))) + .total(Long.parseLong(sb(stdout, "total:", SPACE))) + .main(Long.parseLong(sb(stdout, "main:", SPACE))) + .totalWeight(new BigInteger(sb(stdout, "total_weight:", SPACE))) + .validators(validators) + .build()) + .build(); + } + + /** next validators */ + public static ResultConfig36 parseConfig36(String stdout) { + + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + List validators = new ArrayList<>(); + + if (stdout.contains("ConfigParam(36) = (null)")) { + return ResultConfig36.builder() + .validators( + Validators.builder() + .since(0L) + .until(0L) + .total(0L) + .main(0L) + .totalWeight(BigInteger.ZERO) + .validators(validators) + .build()) + .build(); + } - validators = parseConfigValidators(stdout); - - return ResultConfig36.builder() - .validators(Validators.builder() - .since(Long.parseLong(sb(stdout, "utime_since:", SPACE))) - .until(Long.parseLong(sb(stdout, "utime_until:", SPACE))) - .total(Long.parseLong(sb(stdout, "total:", SPACE))) - .main(Long.parseLong(sb(stdout, "main:", SPACE))) - .totalWeight(new BigInteger(sb(stdout, "total_weight:", SPACE))) - .validators(validators) - .build()) - .build(); - } - - /** - * previous validators - */ - public static ResultConfig32 parseConfig32(String stdout) { - - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - - List validators = new ArrayList<>(); - - if (stdout.contains("ConfigParam(32) = (null)")) { - return ResultConfig32.builder() - .validators(Validators.builder() - .since(0L) - .until(0L) - .total(0L) - .main(0L) - .totalWeight(BigInteger.ZERO) - .validators(validators) - .build()) - .build(); - } + validators = parseConfigValidators(stdout); + + return ResultConfig36.builder() + .validators( + Validators.builder() + .since(Long.parseLong(sb(stdout, "utime_since:", SPACE))) + .until(Long.parseLong(sb(stdout, "utime_until:", SPACE))) + .total(Long.parseLong(sb(stdout, "total:", SPACE))) + .main(Long.parseLong(sb(stdout, "main:", SPACE))) + .totalWeight(new BigInteger(sb(stdout, "total_weight:", SPACE))) + .validators(validators) + .build()) + .build(); + } + + /** previous validators */ + public static ResultConfig32 parseConfig32(String stdout) { + + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + List validators = new ArrayList<>(); + + if (stdout.contains("ConfigParam(32) = (null)")) { + return ResultConfig32.builder() + .validators( + Validators.builder() + .since(0L) + .until(0L) + .total(0L) + .main(0L) + .totalWeight(BigInteger.ZERO) + .validators(validators) + .build()) + .build(); + } - validators = parseConfigValidators(stdout); - - return ResultConfig32.builder() - .validators(Validators.builder() - .since(Long.parseLong(sb(stdout, "utime_since:", SPACE))) - .until(Long.parseLong(sb(stdout, "utime_until:", SPACE))) - .total(Long.parseLong(sb(stdout, "total:", SPACE))) - .main(Long.parseLong(sb(stdout, "main:", SPACE))) - .totalWeight(new BigInteger(sb(stdout, "total_weight:", SPACE))) - .validators(validators) - .build()) - .build(); - } - - public static List parseAllShards(String stdout) throws IncompleteDump, ParsingError { - - if (StringUtils.isEmpty(stdout) || stdout.contains("cannot load state for") || stdout.contains("state already gc'd")) - throw new IncompleteDump("parseAllShards: incomplete dump"); - - try { - String onlyShards = stdout.substring(stdout.indexOf("shard #")); - String[] lines = onlyShards.split("\\r?\\n"); - - List shards = new ArrayList<>(); - - for (String line : lines) { - if (line.startsWith("shard")) { - //BigInteger seqno = new BigInteger(sb(line, "shard #", ":").trim()); - String fullBlockSeqno = sb(line, ":", "@").trim(); - String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; - String rootHash = sb(fullBlockSeqno, ":", ":"); - String fileHash = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); - String shard = sb(shortBlockSeqno, ",", ","); - BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); - Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); - - Long timestamp = Long.parseLong(sb(line, "@", "lt").trim()); - //BigInteger startLt = new BigInteger(sb(line, "lt", "..").trim()); - //BigInteger endLt = new BigInteger(line.substring(line.indexOf(".. ") + 3).trim()); - - ResultLastBlock resultLastBlock = ResultLastBlock.builder() - .wc(wc) - .shard(shard) - .seqno(pureBlockSeqno) - .rootHash(rootHash) - .fileHash(fileHash) - .createdAt(timestamp) - .build(); - - shards.add(resultLastBlock); - } - } - - return shards; - } catch (Exception e) { - throw new ParsingError("parseAllShards: parsing error", e); - } + validators = parseConfigValidators(stdout); + + return ResultConfig32.builder() + .validators( + Validators.builder() + .since(Long.parseLong(sb(stdout, "utime_since:", SPACE))) + .until(Long.parseLong(sb(stdout, "utime_until:", SPACE))) + .total(Long.parseLong(sb(stdout, "total:", SPACE))) + .main(Long.parseLong(sb(stdout, "main:", SPACE))) + .totalWeight(new BigInteger(sb(stdout, "total_weight:", SPACE))) + .validators(validators) + .build()) + .build(); + } + + public static List parseAllShards(String stdout) + throws IncompleteDump, ParsingError { + + if (StringUtils.isEmpty(stdout) + || stdout.contains("cannot load state for") + || stdout.contains("state already gc'd")) + throw new IncompleteDump("parseAllShards: incomplete dump"); + + try { + String onlyShards = stdout.substring(stdout.indexOf("shard #")); + String[] lines = onlyShards.split("\\r?\\n"); + + List shards = new ArrayList<>(); + + for (String line : lines) { + if (line.startsWith("shard")) { + // BigInteger seqno = new BigInteger(sb(line, "shard #", ":").trim()); + String fullBlockSeqno = sb(line, ":", "@").trim(); + String shortBlockSeqno = OPEN + sb(fullBlockSeqno, OPEN, CLOSE) + CLOSE; + String rootHash = sb(fullBlockSeqno, ":", ":"); + String fileHash = fullBlockSeqno.substring(fullBlockSeqno.lastIndexOf(':') + 1); + String shard = sb(shortBlockSeqno, ",", ","); + BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); + Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); + + Long timestamp = Long.parseLong(sb(line, "@", "lt").trim()); + // BigInteger startLt = new BigInteger(sb(line, "lt", "..").trim()); + // BigInteger endLt = new BigInteger(line.substring(line.indexOf(".. ") + 3).trim()); + + ResultLastBlock resultLastBlock = + ResultLastBlock.builder() + .wc(wc) + .shard(shard) + .seqno(pureBlockSeqno) + .rootHash(rootHash) + .fileHash(fileHash) + .createdAt(timestamp) + .build(); + + shards.add(resultLastBlock); + } + } + + return shards; + } catch (Exception e) { + throw new ParsingError("parseAllShards: parsing error", e); } + } - public static Block parseDumpblock(String stdout, boolean includeShardState, boolean includeMessageBody) throws IncompleteDump, ParsingError { + public static Block parseDumpblock( + String stdout, boolean includeShardState, boolean includeMessageBody) + throws IncompleteDump, ParsingError { - if (StringUtils.isEmpty(stdout) || stdout.length() < 400) - throw new IncompleteDump("parseDumpblock: incomplete dump"); + if (StringUtils.isEmpty(stdout) || stdout.length() < 400) + throw new IncompleteDump("parseDumpblock: incomplete dump"); - try { - String blockdump = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + try { + String blockdump = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - Long globalBlockId = Long.parseLong(sb(blockdump, "block global_id:", SPACE)); + Long globalBlockId = Long.parseLong(sb(blockdump, "block global_id:", SPACE)); - String blockInf = sbb(blockdump, "info:(block_info"); - String valueFlw = sbb(blockdump, "value_flow:(value_flow"); + String blockInf = sbb(blockdump, "info:(block_info"); + String valueFlw = sbb(blockdump, "value_flow:(value_flow"); - String shardState = null; - if (includeShardState) { - shardState = sbb(blockdump, "state_update:(raw@(MERKLE_UPDATE"); - } + String shardState = null; + if (includeShardState) { + shardState = sbb(blockdump, "state_update:(raw@(MERKLE_UPDATE"); + } - String blockExtra = sbb(blockdump, "extra:(block_extra"); - Info info = parseBlockInfo(blockInf); - ValueFlow valueFlow = parseValueFlow(valueFlw); - Extra extra = parseExtra(blockExtra, includeMessageBody); + String blockExtra = sbb(blockdump, "extra:(block_extra"); + Info info = parseBlockInfo(blockInf); + ValueFlow valueFlow = parseValueFlow(valueFlw); + Extra extra = parseExtra(blockExtra, includeMessageBody); - return Block.builder() - .globalId(globalBlockId) - .info(info) - .valueFlow(valueFlow) - .shardState(shardState) - .extra(extra) - .build(); + return Block.builder() + .globalId(globalBlockId) + .info(info) + .valueFlow(valueFlow) + .shardState(shardState) + .extra(extra) + .build(); - } catch (Exception e) { - throw new ParsingError("parseDumpblock: parsing error", e); - } + } catch (Exception e) { + throw new ParsingError("parseDumpblock: parsing error", e); } + } - private static Extra parseExtra(String blockExtra, Boolean includeMessageBody) { + private static Extra parseExtra(String blockExtra, Boolean includeMessageBody) { - String inMsgDesc = sbb(blockExtra, "in_msg_descr:("); - String outMsgDesc = sbb(blockExtra, "out_msg_descr:("); - String accountBlocks = sbb(blockExtra, "account_blocks:("); - String masterchainCustomBlock = sbb(blockExtra, "custom:(just"); + String inMsgDesc = sbb(blockExtra, "in_msg_descr:("); + String outMsgDesc = sbb(blockExtra, "out_msg_descr:("); + String accountBlocks = sbb(blockExtra, "account_blocks:("); + String masterchainCustomBlock = sbb(blockExtra, "custom:(just"); - InMsgDescr inMsgDescr = parseInMsgDescr(inMsgDesc, includeMessageBody); + InMsgDescr inMsgDescr = parseInMsgDescr(inMsgDesc, includeMessageBody); - OutMsgDescr outMsgDescr = parseOutMsgDescr(outMsgDesc, includeMessageBody); + OutMsgDescr outMsgDescr = parseOutMsgDescr(outMsgDesc, includeMessageBody); - AccountBlock accountBlock = parseAccountBlock(accountBlocks, includeMessageBody); - String randSeed = sb(blockExtra, "rand_seed:", SPACE); - if (Strings.isNotEmpty(randSeed)) { - randSeed = randSeed.substring(1); - } - String createdBy = sb(blockExtra, "created_by:", SPACE); - if (Strings.isNotEmpty(createdBy)) { - createdBy = createdBy.substring(1); - } - - MasterchainBlock masterchainBlock = parseMasterchainBlock(masterchainCustomBlock, includeMessageBody); - - return Extra.builder() - .inMsgDescrs(inMsgDescr) - .outMsgsDescrs(outMsgDescr) - .accountBlock(accountBlock) - .masterchainBlock(masterchainBlock) - .randSeed(randSeed) - .createdBy(createdBy) - .build(); - } - - private static MasterchainBlock parseMasterchainBlock(String masterchainBlock, boolean includeMessageBody) { - - String hashesBlock = sbb(masterchainBlock, "shard_hashes:("); - Long wc = parseLongSpace(sbb(hashesBlock, "label:(hml_same"), "v:"); - List shards = LiteClientParser.findStringBlocks(hashesBlock, "leaf:(shard_descr"); - - List shardHashes = parseShardHashes(shards, wc); - List shardFees = parseShardFees(shards, wc); // TODO need example dump - - //recover create msg - String recoverCreateMsg = sbb(masterchainBlock, "recover_create_msg:("); - List txsRecoverCreateMsg = parseTxs(recoverCreateMsg, includeMessageBody); - Message inMsgRecoverCreateMsg = parseInMessage(recoverCreateMsg, includeMessageBody); - RecoverCreateMessage recoverCreateMessage = RecoverCreateMessage.builder() - .transactions(txsRecoverCreateMsg) - .inMsg(inMsgRecoverCreateMsg) - .build(); - - //mint msg - String mintMsg = sbb(masterchainBlock, "mint_msg:("); - List txsMintMsg = parseTxs(mintMsg, includeMessageBody); - Message inMsgMintMsg = parseInMessage(mintMsg, includeMessageBody); - MintMessage mintMessage = MintMessage.builder() - .transactions(txsMintMsg) - .inMsg(inMsgMintMsg).build(); - - return MasterchainBlock.builder() - .wc(wc) - .shardHashes(shardHashes) - .shardFees(shardFees) - .recoverCreateMsg(recoverCreateMessage) - .mintMsg(mintMessage) - .build(); - } - - private static List parseShardFees(List shards, Long workchain) { - List shardFees = new ArrayList<>(); - return shardFees; - } - - private static List parseShardHashes(List shards, Long workchain) { - - List shardHashes = new ArrayList<>(); - - for (String shard : shards) { - - BigInteger seqno = parseBigIntegerSpace(shard, SEQ_NO_COLON); - BigInteger regMcSeqno = parseBigIntegerSpace(shard, "reg_mc_seqno:"); - BigInteger startLt = parseBigIntegerSpace(shard, START_LT_COLON); - BigInteger endLt = parseBigIntegerSpace(shard, END_LT_COLON); - String rootHash = sb(shard, "root_hash:", SPACE); - String fileHash = sb(shard, "file_hash:", SPACE); - Byte beforeSplit = parseByteSpace(shard, "before_split:"); - Byte beforeMerge = parseByteSpace(shard, "before_merge:"); - Byte wantSplit = parseByteSpace(shard, "want_split:"); - Byte wantMerge = parseByteSpace(shard, "want_merge:"); - Byte nxCcUpdated = parseByteSpace(shard, "nx_cc_updated:"); - Byte flags = parseByteSpace(shard, "flags:"); - BigInteger nextCatchainSeqno = parseBigIntegerSpace(shard, "next_catchain_seqno:"); - String nextValidatorShard = sb(shard, "next_validator_shard:", SPACE); - BigInteger minRefMcSeqno = parseBigIntegerSpace(shard, "min_ref_mc_seqno:"); - Long getUtime = parseLongSpace(shard, "gen_utime:"); - Value feesCollected = readValue(sbb(shard, "fees_collected:(currencies")); - Value fundsCreated = readValue(sbb(shard, "funds_created:(currencies")); - - ShardHash shardHash = ShardHash.builder() - .wc(workchain) - .seqno(seqno) - .regMcSeqno(regMcSeqno) - .startLt(startLt) - .endLt(endLt) - .rootHash(rootHash) - .fileHash(fileHash) - .beforeSplit(beforeSplit) - .beforeMerge(beforeMerge) - .wantSplit(wantSplit) - .wantMerge(wantMerge) - .nxCcUpdate(nxCcUpdated) - .flags(flags) - .nextCatchainSeqno(nextCatchainSeqno) - .nextValidatorShard(nextValidatorShard) - .minRefMcSeqno(minRefMcSeqno) - .genUtime(getUtime) - .feesCollected(feesCollected) - .fundsCreated(fundsCreated) - .build(); - - shardHashes.add(shardHash); - } - return shardHashes; + AccountBlock accountBlock = parseAccountBlock(accountBlocks, includeMessageBody); + String randSeed = sb(blockExtra, "rand_seed:", SPACE); + if (Strings.isNotEmpty(randSeed)) { + randSeed = randSeed.substring(1); } - - private static AccountBlock parseAccountBlock(String accountBlocks, Boolean includeMessageBody) { - List txsList = LiteClientParser.findStringBlocks(accountBlocks, "value:^(transaction"); - if (!txsList.isEmpty()) { - List txs = txsList.stream().map(x -> LiteClientParser.parseTransaction(x, includeMessageBody)).collect(Collectors.toList()); - return AccountBlock.builder().transactions(txs).build(); - } else { - return AccountBlock.builder().transactions(Collections.emptyList()).build(); - } + String createdBy = sb(blockExtra, "created_by:", SPACE); + if (Strings.isNotEmpty(createdBy)) { + createdBy = createdBy.substring(1); } - private static List parseTxs(String content, Boolean includeMessageBody) { - List txs = new ArrayList<>(); - List txsList = LiteClientParser.findStringBlocks(content, "transaction:("); - if (!txsList.isEmpty()) { - txs = txsList.stream() - .map(x -> LiteClientParser.parseTransaction(x, includeMessageBody)) - .collect(Collectors.toList()); - } - return txs; + MasterchainBlock masterchainBlock = + parseMasterchainBlock(masterchainCustomBlock, includeMessageBody); + + return Extra.builder() + .inMsgDescrs(inMsgDescr) + .outMsgsDescrs(outMsgDescr) + .accountBlock(accountBlock) + .masterchainBlock(masterchainBlock) + .randSeed(randSeed) + .createdBy(createdBy) + .build(); + } + + private static MasterchainBlock parseMasterchainBlock( + String masterchainBlock, boolean includeMessageBody) { + + String hashesBlock = sbb(masterchainBlock, "shard_hashes:("); + Long wc = parseLongSpace(sbb(hashesBlock, "label:(hml_same"), "v:"); + List shards = LiteClientParser.findStringBlocks(hashesBlock, "leaf:(shard_descr"); + + List shardHashes = parseShardHashes(shards, wc); + List shardFees = parseShardFees(shards, wc); // TODO need example dump + + // recover create msg + String recoverCreateMsg = sbb(masterchainBlock, "recover_create_msg:("); + List txsRecoverCreateMsg = parseTxs(recoverCreateMsg, includeMessageBody); + Message inMsgRecoverCreateMsg = parseInMessage(recoverCreateMsg, includeMessageBody); + RecoverCreateMessage recoverCreateMessage = + RecoverCreateMessage.builder() + .transactions(txsRecoverCreateMsg) + .inMsg(inMsgRecoverCreateMsg) + .build(); + + // mint msg + String mintMsg = sbb(masterchainBlock, "mint_msg:("); + List txsMintMsg = parseTxs(mintMsg, includeMessageBody); + Message inMsgMintMsg = parseInMessage(mintMsg, includeMessageBody); + MintMessage mintMessage = + MintMessage.builder().transactions(txsMintMsg).inMsg(inMsgMintMsg).build(); + + return MasterchainBlock.builder() + .wc(wc) + .shardHashes(shardHashes) + .shardFees(shardFees) + .recoverCreateMsg(recoverCreateMessage) + .mintMsg(mintMessage) + .build(); + } + + private static List parseShardFees(List shards, Long workchain) { + List shardFees = new ArrayList<>(); + return shardFees; + } + + private static List parseShardHashes(List shards, Long workchain) { + + List shardHashes = new ArrayList<>(); + + for (String shard : shards) { + + BigInteger seqno = parseBigIntegerSpace(shard, SEQ_NO_COLON); + BigInteger regMcSeqno = parseBigIntegerSpace(shard, "reg_mc_seqno:"); + BigInteger startLt = parseBigIntegerSpace(shard, START_LT_COLON); + BigInteger endLt = parseBigIntegerSpace(shard, END_LT_COLON); + String rootHash = sb(shard, "root_hash:", SPACE); + String fileHash = sb(shard, "file_hash:", SPACE); + Byte beforeSplit = parseByteSpace(shard, "before_split:"); + Byte beforeMerge = parseByteSpace(shard, "before_merge:"); + Byte wantSplit = parseByteSpace(shard, "want_split:"); + Byte wantMerge = parseByteSpace(shard, "want_merge:"); + Byte nxCcUpdated = parseByteSpace(shard, "nx_cc_updated:"); + Byte flags = parseByteSpace(shard, "flags:"); + BigInteger nextCatchainSeqno = parseBigIntegerSpace(shard, "next_catchain_seqno:"); + String nextValidatorShard = sb(shard, "next_validator_shard:", SPACE); + BigInteger minRefMcSeqno = parseBigIntegerSpace(shard, "min_ref_mc_seqno:"); + Long getUtime = parseLongSpace(shard, "gen_utime:"); + Value feesCollected = readValue(sbb(shard, "fees_collected:(currencies")); + Value fundsCreated = readValue(sbb(shard, "funds_created:(currencies")); + + ShardHash shardHash = + ShardHash.builder() + .wc(workchain) + .seqno(seqno) + .regMcSeqno(regMcSeqno) + .startLt(startLt) + .endLt(endLt) + .rootHash(rootHash) + .fileHash(fileHash) + .beforeSplit(beforeSplit) + .beforeMerge(beforeMerge) + .wantSplit(wantSplit) + .wantMerge(wantMerge) + .nxCcUpdate(nxCcUpdated) + .flags(flags) + .nextCatchainSeqno(nextCatchainSeqno) + .nextValidatorShard(nextValidatorShard) + .minRefMcSeqno(minRefMcSeqno) + .genUtime(getUtime) + .feesCollected(feesCollected) + .fundsCreated(fundsCreated) + .build(); + + shardHashes.add(shardHash); } + return shardHashes; + } + + private static AccountBlock parseAccountBlock(String accountBlocks, Boolean includeMessageBody) { + List txsList = LiteClientParser.findStringBlocks(accountBlocks, "value:^(transaction"); + if (!txsList.isEmpty()) { + List txs = + txsList.stream() + .map(x -> LiteClientParser.parseTransaction(x, includeMessageBody)) + .collect(Collectors.toList()); + return AccountBlock.builder().transactions(txs).build(); + } else { + return AccountBlock.builder().transactions(Collections.emptyList()).build(); + } + } + + private static List parseTxs(String content, Boolean includeMessageBody) { + List txs = new ArrayList<>(); + List txsList = LiteClientParser.findStringBlocks(content, "transaction:("); + if (!txsList.isEmpty()) { + txs = + txsList.stream() + .map(x -> LiteClientParser.parseTransaction(x, includeMessageBody)) + .collect(Collectors.toList()); + } + return txs; + } - private static OutMsgDescr parseOutMsgDescr(String outMsgDescr, Boolean includeMessageBody) { - List unparsedLeafs = findLeafsWithLabel(outMsgDescr, "node:(ahmn_leaf"); + private static OutMsgDescr parseOutMsgDescr(String outMsgDescr, Boolean includeMessageBody) { + List unparsedLeafs = findLeafsWithLabel(outMsgDescr, "node:(ahmn_leaf"); - List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); + List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); - return OutMsgDescr.builder() - .leaf(parsedLeafs) - .build(); - } + return OutMsgDescr.builder().leaf(parsedLeafs).build(); + } - private static InMsgDescr parseInMsgDescr(String inMsgDesc, boolean includeMessageBody) { + private static InMsgDescr parseInMsgDescr(String inMsgDesc, boolean includeMessageBody) { - List unparsedLeafs = findLeafsWithLabel(inMsgDesc, "node:(ahmn_leaf"); + List unparsedLeafs = findLeafsWithLabel(inMsgDesc, "node:(ahmn_leaf"); - List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); + List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); - return InMsgDescr.builder() - .leaf(parsedLeafs) - .build(); - } + return InMsgDescr.builder().leaf(parsedLeafs).build(); + } - private static List parseLeafs(List unparsedLeafs, boolean includeMessageBody) { - List result = new ArrayList<>(); - for (String unparsedLeaf : unparsedLeafs) { - String label = readLabel(unparsedLeaf); - Message inMsg = parseInMessage(unparsedLeaf, includeMessageBody); - List txs = parseTxs(unparsedLeaf, includeMessageBody); - BigDecimal feesCollected = parseBigDecimalBracket(sbb(unparsedLeaf, "fees_collected:("), VALUE_COLON); - Value valueImported = readValue(sbb(unparsedLeaf, "value_imported:(currencies")); + private static List parseLeafs(List unparsedLeafs, boolean includeMessageBody) { + List result = new ArrayList<>(); + for (String unparsedLeaf : unparsedLeafs) { + String label = readLabel(unparsedLeaf); + Message inMsg = parseInMessage(unparsedLeaf, includeMessageBody); + List txs = parseTxs(unparsedLeaf, includeMessageBody); + BigDecimal feesCollected = + parseBigDecimalBracket(sbb(unparsedLeaf, "fees_collected:("), VALUE_COLON); + Value valueImported = readValue(sbb(unparsedLeaf, "value_imported:(currencies")); - Leaf leaf = Leaf.builder() - .label(label) - .message(inMsg) - .transactions(txs) - .feesCollected(feesCollected) - .valueImported(valueImported) - .build(); - result.add(leaf); - } - return result; + Leaf leaf = + Leaf.builder() + .label(label) + .message(inMsg) + .transactions(txs) + .feesCollected(feesCollected) + .valueImported(valueImported) + .build(); + result.add(leaf); } - - private static String readLabel(String unparsedLeaf) { - String result = null; - try { - String label = sbb(unparsedLeaf, "label:("); - result = label.substring(label.indexOf("s:x") + 3, label.length() - 2); - } catch (Exception e) { - log.info("cannot parse label in extra block"); - } - return result; - } - - private static Message parseInMessage(String inMsgDescr, boolean includeMessageBody) { - - String message = sbb(inMsgDescr, "(message"); - - if (Strings.isNotEmpty(message)) { - // message exist - String msgType = convertMsgType(sb(message, "info:(", SPACE)); - Byte ihrDisabled = parseByteSpace(message, "ihr_disabled:"); - Byte bounce = parseByteSpace(message, BOUNCE_COLON); - Byte bounced = parseByteSpace(message, "bounced:"); - BigInteger createdLt = parseBigIntegerSpace(message, "created_lt:"); - BigInteger createdAt = parseBigIntegerBracket(message, "created_at:"); - - String src = sbb(message, "src:("); - Long srcWc = parseLongSpace(src, WORKCHAIN_ID_COLON); - String srcAddr = sb(src, ADDRESS_COLON, CLOSE); - if (Strings.isNotEmpty(srcAddr)) { - srcAddr = srcAddr.substring(1); - } - LiteClientAddress sourceAddr = LiteClientAddress.builder().wc(srcWc).addr(srcAddr).build(); - - String dest = sbb(message, "dest:("); - Long destWc = parseLongSpace(dest, WORKCHAIN_ID_COLON); - String destAddr = sb(dest, ADDRESS_COLON, CLOSE); - if (Strings.isNotEmpty(destAddr)) { - destAddr = destAddr.substring(1); - } - LiteClientAddress destinationAddr = LiteClientAddress.builder().wc(destWc).addr(destAddr).build(); - - Value grams = readValue(sbb(inMsgDescr, "value:(currencies")); -//add import_fee handling - BigDecimal ihrFee = parseBigDecimalBracket(sbb(message, "ihr_fee:("), VALUE_COLON); - BigDecimal fwdFee = parseBigDecimalBracket(sbb(message, "fwd_fee:("), VALUE_COLON); - - BigDecimal importFee = parseBigDecimalBracket(sbb(message, "import_fee:("), VALUE_COLON); - - String initContent = sbb(message, "init:("); - Init init = parseInit(initContent); - - Body body = null; - if (includeMessageBody) { - String bodyContent = sbb(message, "body:("); - body = parseBody(bodyContent); - } - - return Message.builder() - .srcAddr(sourceAddr) - .destAddr(destinationAddr) - .type(msgType) - .ihrDisabled(ihrDisabled) - .bounce(bounce) - .bounced(bounced) - .value(grams) - .ihrFee(ihrFee) - .fwdFee(fwdFee) - .importFee(importFee) - .createdLt(createdLt) - .createdAt(createdAt) - .init(init) - .body(body) - .build(); - - } else { - return null; - } + return result; + } + + private static String readLabel(String unparsedLeaf) { + String result = null; + try { + String label = sbb(unparsedLeaf, "label:("); + result = label.substring(label.indexOf("s:x") + 3, label.length() - 2); + } catch (Exception e) { + log.info("cannot parse label in extra block"); } - - private static Body parseBody(String bodyContent) { - if (StringUtils.isEmpty(bodyContent)) { - return Body.builder() - .cells(new ArrayList<>()) - .build(); - } - String[] bodyCells = bodyContent.split("x\\{"); - List cells = new ArrayList<>(Arrays.asList(bodyCells)); - List cleanedCells = cleanCells(cells); - - return Body.builder() - .cells(cleanedCells) - .build(); + return result; + } + + private static Message parseInMessage(String inMsgDescr, boolean includeMessageBody) { + + String message = sbb(inMsgDescr, "(message"); + + if (Strings.isNotEmpty(message)) { + // message exist + String msgType = convertMsgType(sb(message, "info:(", SPACE)); + Byte ihrDisabled = parseByteSpace(message, "ihr_disabled:"); + Byte bounce = parseByteSpace(message, BOUNCE_COLON); + Byte bounced = parseByteSpace(message, "bounced:"); + BigInteger createdLt = parseBigIntegerSpace(message, "created_lt:"); + BigInteger createdAt = parseBigIntegerBracket(message, "created_at:"); + + String src = sbb(message, "src:("); + Long srcWc = parseLongSpace(src, WORKCHAIN_ID_COLON); + String srcAddr = sb(src, ADDRESS_COLON, CLOSE); + if (Strings.isNotEmpty(srcAddr)) { + srcAddr = srcAddr.substring(1); + } + LiteClientAddress sourceAddr = LiteClientAddress.builder().wc(srcWc).addr(srcAddr).build(); + + String dest = sbb(message, "dest:("); + Long destWc = parseLongSpace(dest, WORKCHAIN_ID_COLON); + String destAddr = sb(dest, ADDRESS_COLON, CLOSE); + if (Strings.isNotEmpty(destAddr)) { + destAddr = destAddr.substring(1); + } + LiteClientAddress destinationAddr = + LiteClientAddress.builder().wc(destWc).addr(destAddr).build(); + + Value grams = readValue(sbb(inMsgDescr, "value:(currencies")); + // add import_fee handling + BigDecimal ihrFee = parseBigDecimalBracket(sbb(message, "ihr_fee:("), VALUE_COLON); + BigDecimal fwdFee = parseBigDecimalBracket(sbb(message, "fwd_fee:("), VALUE_COLON); + + BigDecimal importFee = parseBigDecimalBracket(sbb(message, "import_fee:("), VALUE_COLON); + + String initContent = sbb(message, "init:("); + Init init = parseInit(initContent); + + Body body = null; + if (includeMessageBody) { + String bodyContent = sbb(message, "body:("); + body = parseBody(bodyContent); + } + + return Message.builder() + .srcAddr(sourceAddr) + .destAddr(destinationAddr) + .type(msgType) + .ihrDisabled(ihrDisabled) + .bounce(bounce) + .bounced(bounced) + .value(grams) + .ihrFee(ihrFee) + .fwdFee(fwdFee) + .importFee(importFee) + .createdLt(createdLt) + .createdAt(createdAt) + .init(init) + .body(body) + .build(); + + } else { + return null; } + } - private static List cleanCells(List cells) { - if (cells == null || cells.isEmpty()) { - return new ArrayList<>(); - } - - cells.remove(0); - - return cells.stream() - .map(s -> s.replaceAll("[)} ]", "")) - .filter(s -> !s.isEmpty()) - .collect(Collectors.toList()); + private static Body parseBody(String bodyContent) { + if (StringUtils.isEmpty(bodyContent)) { + return Body.builder().cells(new ArrayList<>()).build(); } + String[] bodyCells = bodyContent.split("x\\{"); + List cells = new ArrayList<>(Arrays.asList(bodyCells)); + List cleanedCells = cleanCells(cells); - private static Init parseInit(String initContent) { - if (StringUtils.isEmpty(initContent)) { - return Init.builder() - .code(new ArrayList<>()) - .data(new ArrayList<>()) - .library(new ArrayList<>()) - .build(); - } - String code = sbb(initContent, "code:("); - String[] codeCells = isNull(code) ? new String[0] : code.split("x\\{"); - List cleanedCodeCells = cleanCells(new ArrayList<>(Arrays.asList(codeCells))); - - String data = sbb(initContent, "data:("); - String[] dataCells = isNull(data) ? new String[0] : data.split("x\\{"); - List cleanedDataCells = cleanCells(new ArrayList<>(Arrays.asList(dataCells))); + return Body.builder().cells(cleanedCells).build(); + } - String library = sbb(initContent, "library:("); - String[] libraryCells = isNull(library) ? new String[0] : library.split("x\\{"); - List cleanedLibraryCells = cleanCells(new ArrayList<>(Arrays.asList(libraryCells))); + private static List cleanCells(List cells) { + if (cells == null || cells.isEmpty()) { + return new ArrayList<>(); + } - return Init.builder() - .code(cleanedCodeCells) - .data(cleanedDataCells) - .library(cleanedLibraryCells) - .build(); + cells.remove(0); + + return cells.stream() + .map(s -> s.replaceAll("[)} ]", "")) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + private static Init parseInit(String initContent) { + if (StringUtils.isEmpty(initContent)) { + return Init.builder() + .code(new ArrayList<>()) + .data(new ArrayList<>()) + .library(new ArrayList<>()) + .build(); + } + String code = sbb(initContent, "code:("); + String[] codeCells = isNull(code) ? new String[0] : code.split("x\\{"); + List cleanedCodeCells = cleanCells(new ArrayList<>(Arrays.asList(codeCells))); + + String data = sbb(initContent, "data:("); + String[] dataCells = isNull(data) ? new String[0] : data.split("x\\{"); + List cleanedDataCells = cleanCells(new ArrayList<>(Arrays.asList(dataCells))); + + String library = sbb(initContent, "library:("); + String[] libraryCells = isNull(library) ? new String[0] : library.split("x\\{"); + List cleanedLibraryCells = cleanCells(new ArrayList<>(Arrays.asList(libraryCells))); + + return Init.builder() + .code(cleanedCodeCells) + .data(cleanedDataCells) + .library(cleanedLibraryCells) + .build(); + } + + private static List parseOutMessages(String outMsgsField, boolean includeMessageBody) { + + List outMsgs = new ArrayList<>(); + if (StringUtils.isEmpty(outMsgsField)) { + return outMsgs; } - private static List parseOutMessages(String outMsgsField, boolean includeMessageBody) { + while (outMsgsField.contains("(message")) { - List outMsgs = new ArrayList<>(); - if (StringUtils.isEmpty(outMsgsField)) { - return outMsgs; - } + String message = sbb(outMsgsField, "(message"); - while (outMsgsField.contains("(message")) { - - String message = sbb(outMsgsField, "(message"); - - if (Strings.isNotEmpty(message)) { - // message exist - String msgType = convertMsgType(sb(message, "info:(", SPACE)); - Byte ihrDisabled = parseByteSpace(message, "ihr_disabled:"); - Byte bounce = parseByteSpace(message, BOUNCE_COLON); - Byte bounced = parseByteSpace(message, "bounced:"); - BigInteger createdLt = parseBigIntegerSpace(message, "created_lt:"); - BigInteger createdAt = parseBigIntegerBracket(message, "created_at:"); - - String src = sbb(message, "src:("); - Long srcWc = parseLongSpace(src, WORKCHAIN_ID_COLON); - String srcAddr = sb(src, ADDRESS_COLON, CLOSE); - if (Strings.isNotEmpty(srcAddr)) { - srcAddr = srcAddr.substring(1); - } - LiteClientAddress sourceAddr = LiteClientAddress.builder().wc(srcWc).addr(srcAddr).build(); - - String dest = sbb(message, "dest:("); - Long destWc = parseLongSpace(dest, WORKCHAIN_ID_COLON); - String destAddr = sb(dest, ADDRESS_COLON, CLOSE); - if (Strings.isNotEmpty(destAddr)) { - destAddr = destAddr.substring(1); - } - LiteClientAddress destinationAddr = LiteClientAddress.builder().wc(destWc).addr(destAddr).build(); - - Value toncoins = readValue(sbb(message, "value:(currencies")); - - BigDecimal ihrFee = parseBigDecimalBracket(sbb(message, "ihr_fee:("), VALUE_COLON); - BigDecimal fwdFee = parseBigDecimalBracket(sbb(message, "fwd_fee:("), VALUE_COLON); - BigDecimal importFee = parseBigDecimalBracket(sbb(message, "import_fee:("), VALUE_COLON); - - String initContent = sbb(message, "init:("); - Init init = parseInit(initContent); - - Body body = null; - if (includeMessageBody) { - String bodyContent = sbb(message, "body:("); - body = parseBody(bodyContent); - } - - outMsgs.add( - Message.builder() - .srcAddr(sourceAddr) - .destAddr(destinationAddr) - .type(msgType) - .ihrDisabled(ihrDisabled) - .bounce(bounce) - .bounced(bounced) - .value(toncoins) - .ihrFee(ihrFee) - .fwdFee(fwdFee) - .importFee(importFee) - .createdLt(createdLt) - .createdAt(createdAt) - .init(init) - .body(body) - .build()); - outMsgsField = outMsgsField.replace(message, ""); - } - } - return outMsgs; - } - - private static String convertMsgType(String type) { - switch (type) { - case "int_msg_info": - return "Internal"; - case "ext_in_msg_info": - return "External In"; - case "ext_out_msg_info": - return "External Out"; - default: - return "Unknown"; - } - } + if (Strings.isNotEmpty(message)) { + // message exist + String msgType = convertMsgType(sb(message, "info:(", SPACE)); + Byte ihrDisabled = parseByteSpace(message, "ihr_disabled:"); + Byte bounce = parseByteSpace(message, BOUNCE_COLON); + Byte bounced = parseByteSpace(message, "bounced:"); + BigInteger createdLt = parseBigIntegerSpace(message, "created_lt:"); + BigInteger createdAt = parseBigIntegerBracket(message, "created_at:"); - private static Transaction parseTransaction(String str, Boolean includeMessageBody) { - - String transaction = sbb(str, "(transaction account_addr"); - - if (StringUtils.isNotEmpty(transaction)) { - - String accountAddr = sb(transaction, "account_addr:", "lt").trim(); - if (Strings.isNotEmpty(accountAddr)) { - accountAddr = accountAddr.substring(1); - } - BigInteger lt = parseBigIntegerSpace(transaction, "lt:"); - Long now = parseLongSpace(transaction, "now:"); - BigInteger prevTxLt = parseBigIntegerSpace(transaction, "prev_trans_lt:"); - String prevTxHash = sb(transaction, "prev_trans_hash:", SPACE).trim(); - if (Strings.isNotEmpty(prevTxHash)) { - prevTxHash = prevTxHash.substring(1); - } - Long outMsgsCnt = parseLongSpace(transaction, "outmsg_cnt:"); - String origStatus = sb(transaction, "orig_status:", SPACE).trim(); - String endStatus = sb(transaction, "end_status:", SPACE).trim(); - - origStatus = convertAccountStatus(origStatus); - endStatus = convertAccountStatus(endStatus); - - String inMsgField = sbb(transaction, "in_msg:"); // small hack - Message inMsg = parseInMessage(inMsgField, includeMessageBody); - - String outMsgsField = sbb(transaction, "out_msgs:"); - List outMsgs = parseOutMessages(outMsgsField, includeMessageBody); - - String stateUpdate = sbb(transaction, "state_update:("); - String oldHash = sb(stateUpdate, "old_hash:", SPACE); - if (Strings.isNotEmpty(oldHash)) { - oldHash = oldHash.substring(1); - } - String newHash = sb(stateUpdate, "new_hash:", CLOSE); - if (Strings.isNotEmpty(newHash)) { - newHash = newHash.substring(1); - } - - Value totalFees = readValue(sbb(transaction, "total_fees:(currencies")); - - String description = sbb(transaction, "description:("); - - TransactionDescription txdDescription = parseTransactionDescription(description); - - return Transaction.builder() - .accountAddr(accountAddr) - .now(now) - .lt(lt) - .prevTxHash(prevTxHash) - .prevTxLt(prevTxLt) - .outMsgsCount(outMsgsCnt) - .origStatus(origStatus) - .endStatus(endStatus) - .inMsg(inMsg) - .outMsgs(outMsgs) - .totalFees(totalFees) - .oldHash(oldHash) - .newHash(newHash) - .description(txdDescription) - .build(); - } else { - return null; + String src = sbb(message, "src:("); + Long srcWc = parseLongSpace(src, WORKCHAIN_ID_COLON); + String srcAddr = sb(src, ADDRESS_COLON, CLOSE); + if (Strings.isNotEmpty(srcAddr)) { + srcAddr = srcAddr.substring(1); } - } + LiteClientAddress sourceAddr = LiteClientAddress.builder().wc(srcWc).addr(srcAddr).build(); - private static String convertAccountStatus(String origStatus) { - String status; - switch (origStatus) { - case "acc_state_uninit": - status = "Uninitialized"; - break; - case "acc_state_frozen": - status = "Frozen"; - break; - case "acc_state_active": - status = "Active"; - break; - case "acc_state_nonexist": - status = "Nonexistent"; - break; - default: - status = "Unknown"; + String dest = sbb(message, "dest:("); + Long destWc = parseLongSpace(dest, WORKCHAIN_ID_COLON); + String destAddr = sb(dest, ADDRESS_COLON, CLOSE); + if (Strings.isNotEmpty(destAddr)) { + destAddr = destAddr.substring(1); } - return status; + LiteClientAddress destinationAddr = + LiteClientAddress.builder().wc(destWc).addr(destAddr).build(); - } + Value toncoins = readValue(sbb(message, "value:(currencies")); - private static TransactionDescription parseTransactionDescription(String description) { + BigDecimal ihrFee = parseBigDecimalBracket(sbb(message, "ihr_fee:("), VALUE_COLON); + BigDecimal fwdFee = parseBigDecimalBracket(sbb(message, "fwd_fee:("), VALUE_COLON); + BigDecimal importFee = parseBigDecimalBracket(sbb(message, "import_fee:("), VALUE_COLON); - BigDecimal creditFirst = parseBigDecimalSpace(description, "credit_first:"); - String storageStr = sbb(description, "storage_ph:("); - String creditStr = sbb(description, "credit_ph:("); - String computeStr = sbb(description, "compute_ph:("); - String actionStr = sbb(description, "action:("); - Byte bounce = parseByteSpace(description, BOUNCE_COLON); - Byte aborted = parseByteSpace(description, "aborted:"); - Byte destroyed = parseByteBracket(description, "destroyed:"); + String initContent = sbb(message, "init:("); + Init init = parseInit(initContent); - Byte tock = parseByteSpace(description, "is_tock:"); - String type = "Tock"; - if (nonNull(tock)) { - if (tock == 0) { - type = "Tick"; - } - } else { - type = "Ordinary"; + Body body = null; + if (includeMessageBody) { + String bodyContent = sbb(message, "body:("); + body = parseBody(bodyContent); } - TransactionStorage txStorage = parseTransactionStorage(storageStr); - TransactionCredit txCredit = parseTransactionCredit(creditStr); - TransactionCompute txCompute = parseTransactionCompute(computeStr); - TransactionAction txAction = parseTransactionAction(actionStr); - - return TransactionDescription.builder() - .aborted(aborted) + outMsgs.add( + Message.builder() + .srcAddr(sourceAddr) + .destAddr(destinationAddr) + .type(msgType) + .ihrDisabled(ihrDisabled) .bounce(bounce) - .destroyed(destroyed) - .action(txAction) - .compute(txCompute) - .credit(txCredit) - .storage(txStorage) - .creditFirst(creditFirst) - .type(type) - .build(); - } - - private static TransactionAction parseTransactionAction(String actionStr) { - if (StringUtils.isEmpty(actionStr)) { - return TransactionAction.builder().build(); - } else { - Byte actionSuccess = parseByteSpace(actionStr, SUCCESS_COLON); - Byte actionValid = parseByteSpace(actionStr, "valid:"); - Byte actionNoFunds = parseByteSpace(actionStr, "no_funds:"); - String statusChanged = sb(actionStr, "status_change:", SPACE); - BigDecimal totalFwdFees = parseBigDecimalBracket(sbb(actionStr, "total_fwd_fees:("), VALUE_COLON); - BigDecimal totalActionFees = parseBigDecimalBracket(sbb(actionStr, "total_action_fees:("), VALUE_COLON); - BigInteger resultCode = parseBigIntegerSpace(actionStr, "result_code:"); - BigInteger resultArg = parseBigIntegerBracket(sbb(actionStr, "result_arg:("), VALUE_COLON); - BigInteger totalActions = parseBigIntegerSpace(actionStr, "tot_actions:"); - BigInteger specActions = parseBigIntegerSpace(actionStr, "spec_actions:"); - BigInteger skippedActions = parseBigIntegerSpace(actionStr, "skipped_actions:"); - BigInteger msgsCreated = parseBigIntegerSpace(actionStr, "msgs_created:"); - String actionListHash = sb(actionStr, "action_list_hash:", SPACE); - if (Strings.isNotEmpty(actionListHash)) { - actionListHash = actionListHash.substring(1); - } - BigInteger totalMsgSizeCells = parseBigIntegerBracket(sbb(sbb(actionStr, "tot_msg_size:("), "cells:("), VALUE_COLON); - BigInteger totalMsgSizeBits = parseBigIntegerBracket(sbb(sbb(actionStr, "tot_msg_size:("), "bits:("), VALUE_COLON); - - return TransactionAction.builder() - .success(actionSuccess) - .valid(actionValid) - .noFunds(actionNoFunds) - .statusChange(statusChanged) - .totalFwdFee(totalFwdFees) - .totalActionFee(totalActionFees) - .resultCode(resultCode) - .resultArg(resultArg) - .totActions(totalActions) - .specActions(specActions) - .skippedActions(skippedActions) - .msgsCreated(msgsCreated) - .actionListHash(actionListHash) - .totalMsgSizeCells(totalMsgSizeCells) - .totalMsgSizeBits(totalMsgSizeBits) - .build(); - } + .bounced(bounced) + .value(toncoins) + .ihrFee(ihrFee) + .fwdFee(fwdFee) + .importFee(importFee) + .createdLt(createdLt) + .createdAt(createdAt) + .init(init) + .body(body) + .build()); + outMsgsField = outMsgsField.replace(message, ""); + } } - - private static TransactionCredit parseTransactionCredit(String creditStr) { - if (StringUtils.isEmpty(creditStr)) { - return TransactionCredit.builder().build(); - } else { - BigDecimal creditDueFeesCollected = parseBigDecimalBracket(sbb(creditStr, "due_fees_collected:("), VALUE_COLON); - Value credit = readValue(sbb(creditStr, "due_fees_collected")); - return TransactionCredit.builder() - .credit(credit) - .dueFeesCollected(creditDueFeesCollected) - .build(); - } + return outMsgs; + } + + private static String convertMsgType(String type) { + switch (type) { + case "int_msg_info": + return "Internal"; + case "ext_in_msg_info": + return "External In"; + case "ext_out_msg_info": + return "External Out"; + default: + return "Unknown"; } - - private static TransactionStorage parseTransactionStorage(String storageStr) { - if (StringUtils.isEmpty(storageStr)) { - return TransactionStorage.builder().build(); - } else { - BigDecimal storageFeesCollected = parseBigDecimalBracket(sbb(storageStr, "(tr_phase_storage"), VALUE_COLON); - BigDecimal dueFees = parseBigDecimalBracket(sbb(storageStr, "storage_fees_due:("), VALUE_COLON); - String storageSccountStatus = sb(storageStr, "status_change:", CLOSE); - return TransactionStorage.builder() - .feesCollected(storageFeesCollected) - .feesDue(dueFees) - .statusChange(storageSccountStatus) - .build(); - } + } + + private static Transaction parseTransaction(String str, Boolean includeMessageBody) { + + String transaction = sbb(str, "(transaction account_addr"); + + if (StringUtils.isNotEmpty(transaction)) { + + String accountAddr = sb(transaction, "account_addr:", "lt").trim(); + if (Strings.isNotEmpty(accountAddr)) { + accountAddr = accountAddr.substring(1); + } + BigInteger lt = parseBigIntegerSpace(transaction, "lt:"); + Long now = parseLongSpace(transaction, "now:"); + BigInteger prevTxLt = parseBigIntegerSpace(transaction, "prev_trans_lt:"); + String prevTxHash = sb(transaction, "prev_trans_hash:", SPACE).trim(); + if (Strings.isNotEmpty(prevTxHash)) { + prevTxHash = prevTxHash.substring(1); + } + Long outMsgsCnt = parseLongSpace(transaction, "outmsg_cnt:"); + String origStatus = sb(transaction, "orig_status:", SPACE).trim(); + String endStatus = sb(transaction, "end_status:", SPACE).trim(); + + origStatus = convertAccountStatus(origStatus); + endStatus = convertAccountStatus(endStatus); + + String inMsgField = sbb(transaction, "in_msg:"); // small hack + Message inMsg = parseInMessage(inMsgField, includeMessageBody); + + String outMsgsField = sbb(transaction, "out_msgs:"); + List outMsgs = parseOutMessages(outMsgsField, includeMessageBody); + + String stateUpdate = sbb(transaction, "state_update:("); + String oldHash = sb(stateUpdate, "old_hash:", SPACE); + if (Strings.isNotEmpty(oldHash)) { + oldHash = oldHash.substring(1); + } + String newHash = sb(stateUpdate, "new_hash:", CLOSE); + if (Strings.isNotEmpty(newHash)) { + newHash = newHash.substring(1); + } + + Value totalFees = readValue(sbb(transaction, "total_fees:(currencies")); + + String description = sbb(transaction, "description:("); + + TransactionDescription txdDescription = parseTransactionDescription(description); + + return Transaction.builder() + .accountAddr(accountAddr) + .now(now) + .lt(lt) + .prevTxHash(prevTxHash) + .prevTxLt(prevTxLt) + .outMsgsCount(outMsgsCnt) + .origStatus(origStatus) + .endStatus(endStatus) + .inMsg(inMsg) + .outMsgs(outMsgs) + .totalFees(totalFees) + .oldHash(oldHash) + .newHash(newHash) + .description(txdDescription) + .build(); + } else { + return null; } - - private static TransactionCompute parseTransactionCompute(String computeStr) { - if (StringUtils.isEmpty(computeStr)) { - return TransactionCompute.builder().build(); - } else { - BigDecimal gasFees = parseBigDecimalBracket(sbb(computeStr, "gas_fees:("), VALUE_COLON); - Byte success = parseByteSpace(computeStr, SUCCESS_COLON); - Byte msgStateUsed = parseByteSpace(computeStr, "msg_state_used:"); - Byte accountActivated = parseByteSpace(computeStr, "account_activated:"); - BigDecimal gasUsed = parseBigDecimalBracket(sbb(computeStr, "gas_used:("), VALUE_COLON); - BigDecimal gasLimit = parseBigDecimalBracket(sbb(computeStr, "gas_limit:("), VALUE_COLON); - BigDecimal gasCredit = parseBigDecimalBracket(sbb(computeStr, "gas_credit:("), VALUE_COLON); // TODO parse gas_credit:(just value:(var_uint len:2 value:10000)) - String exitArgs = sb(computeStr, "exit_arg:", SPACE); - BigInteger exitCode = parseBigIntegerSpace(computeStr, "exit_code:"); - BigInteger mode = parseBigIntegerSpace(computeStr, "mode:"); - BigInteger vmsSteps = parseBigIntegerSpace(computeStr, "vm_steps:"); - String vmInitStateHash = sb(computeStr, "vm_init_state_hash:", SPACE); - if (Strings.isNotEmpty(vmInitStateHash)) { - vmInitStateHash = vmInitStateHash.substring(1); - } - String vmFinalStateHash = sb(computeStr, "vm_final_state_hash:", CLOSE); - if (Strings.isNotEmpty(vmFinalStateHash)) { - vmFinalStateHash = vmFinalStateHash.substring(1); - } - return TransactionCompute.builder() - .gasFees(gasFees) - .gasCredit(gasCredit) - .gasUsed(gasUsed) - .gasLimit(gasLimit) - .accountActivated(accountActivated) - .msgStateUsed(msgStateUsed) - .success(success) - .mode(mode) - .vmSteps(vmsSteps) - .vmInitStateHash(vmInitStateHash) - .vmFinalStateHash(vmFinalStateHash) - .exitArg(exitArgs) - .exitCode(exitCode) - .build(); - } + } + + private static String convertAccountStatus(String origStatus) { + String status; + switch (origStatus) { + case "acc_state_uninit": + status = "Uninitialized"; + break; + case "acc_state_frozen": + status = "Frozen"; + break; + case "acc_state_active": + status = "Active"; + break; + case "acc_state_nonexist": + status = "Nonexistent"; + break; + default: + status = "Unknown"; } - - private static ValueFlow parseValueFlow(String valueFlw) { - - Value prevBlock = readValue(sbb(valueFlw, "from_prev_blk:(currencies")); - Value nextBlock = readValue(sbb(valueFlw, "to_next_blk:(currencies")); - Value imported = readValue(sbb(valueFlw, "imported:(currencies")); - Value exported = readValue(sbb(valueFlw, "exported:(currencies")); - Value feesCollected = readValue(sbb(valueFlw, "fees_collected:(currencies")); - Value feesImported = readValue(sbb(valueFlw, "fees_imported:(currencies")); - Value recovered = readValue(sbb(valueFlw, "recovered:(currencies")); - Value created = readValue(sbb(valueFlw, "created:(currencies")); - Value minted = readValue(sbb(valueFlw, "minted:(currencies")); - - return ValueFlow.builder() - .prevBlock(prevBlock) - .nextBlock(nextBlock) - .imported(imported) - .exported(exported) - .feesCollected(feesCollected) - .feesImported(feesImported) - .recovered(recovered) - .created(created) - .minted(minted) - .build(); - } - - private static Value readValue(String str) { - if (StringUtils.isEmpty(str)) - return Value.builder().toncoins(BigDecimal.ZERO).build(); - - String res = sbb(str, "amount:("); - BigDecimal grams = parseBigDecimalBracket(res, VALUE_COLON); - - List otherCurrencies = new ArrayList<>(); - - if (str.contains("node:(hmn_fork")) { - // exist extra currencies - List currenciesLeft = findStringBlocks(str, "left:(hm_edge"); - List currenciesRight = findStringBlocks(str, "right:(hm_edge"); - List currencies = new ArrayList<>(); - currencies.addAll(currenciesLeft); - currencies.addAll(currenciesRight); - - if (!currencies.isEmpty()) { - for (String cur : currencies) { - Byte len = parseByteSpace(cur, "len:"); - String label = sb(cur, "s:", CLOSE); - if (StringUtils.isNotEmpty(label)) { - label = label.substring(1); - } - if (!isNull(len)) { - BigDecimal value = parseBigDecimalBracket(cur.substring(cur.indexOf("var_uint")), VALUE_COLON); - - Currency currency = Currency.builder() - .label(label) - .len(len) - .value(value) - .build(); - otherCurrencies.add(currency); - } - } - } - } - - return Value.builder() - .toncoins(grams) - .otherCurrencies(otherCurrencies) - .build(); + return status; + } + + private static TransactionDescription parseTransactionDescription(String description) { + + BigDecimal creditFirst = parseBigDecimalSpace(description, "credit_first:"); + String storageStr = sbb(description, "storage_ph:("); + String creditStr = sbb(description, "credit_ph:("); + String computeStr = sbb(description, "compute_ph:("); + String actionStr = sbb(description, "action:("); + Byte bounce = parseByteSpace(description, BOUNCE_COLON); + Byte aborted = parseByteSpace(description, "aborted:"); + Byte destroyed = parseByteBracket(description, "destroyed:"); + + Byte tock = parseByteSpace(description, "is_tock:"); + String type = "Tock"; + if (nonNull(tock)) { + if (tock == 0) { + type = "Tick"; + } + } else { + type = "Ordinary"; } - private static List readLibraries(String str) { - if (StringUtils.isEmpty(str)) { - return Collections.singletonList(Library.builder().build()); - } + TransactionStorage txStorage = parseTransactionStorage(storageStr); + TransactionCredit txCredit = parseTransactionCredit(creditStr); + TransactionCompute txCompute = parseTransactionCompute(computeStr); + TransactionAction txAction = parseTransactionAction(actionStr); + + return TransactionDescription.builder() + .aborted(aborted) + .bounce(bounce) + .destroyed(destroyed) + .action(txAction) + .compute(txCompute) + .credit(txCredit) + .storage(txStorage) + .creditFirst(creditFirst) + .type(type) + .build(); + } + + private static TransactionAction parseTransactionAction(String actionStr) { + if (StringUtils.isEmpty(actionStr)) { + return TransactionAction.builder().build(); + } else { + Byte actionSuccess = parseByteSpace(actionStr, SUCCESS_COLON); + Byte actionValid = parseByteSpace(actionStr, "valid:"); + Byte actionNoFunds = parseByteSpace(actionStr, "no_funds:"); + String statusChanged = sb(actionStr, "status_change:", SPACE); + BigDecimal totalFwdFees = + parseBigDecimalBracket(sbb(actionStr, "total_fwd_fees:("), VALUE_COLON); + BigDecimal totalActionFees = + parseBigDecimalBracket(sbb(actionStr, "total_action_fees:("), VALUE_COLON); + BigInteger resultCode = parseBigIntegerSpace(actionStr, "result_code:"); + BigInteger resultArg = parseBigIntegerBracket(sbb(actionStr, "result_arg:("), VALUE_COLON); + BigInteger totalActions = parseBigIntegerSpace(actionStr, "tot_actions:"); + BigInteger specActions = parseBigIntegerSpace(actionStr, "spec_actions:"); + BigInteger skippedActions = parseBigIntegerSpace(actionStr, "skipped_actions:"); + BigInteger msgsCreated = parseBigIntegerSpace(actionStr, "msgs_created:"); + String actionListHash = sb(actionStr, "action_list_hash:", SPACE); + if (Strings.isNotEmpty(actionListHash)) { + actionListHash = actionListHash.substring(1); + } + BigInteger totalMsgSizeCells = + parseBigIntegerBracket(sbb(sbb(actionStr, "tot_msg_size:("), "cells:("), VALUE_COLON); + BigInteger totalMsgSizeBits = + parseBigIntegerBracket(sbb(sbb(actionStr, "tot_msg_size:("), "bits:("), VALUE_COLON); + + return TransactionAction.builder() + .success(actionSuccess) + .valid(actionValid) + .noFunds(actionNoFunds) + .statusChange(statusChanged) + .totalFwdFee(totalFwdFees) + .totalActionFee(totalActionFees) + .resultCode(resultCode) + .resultArg(resultArg) + .totActions(totalActions) + .specActions(specActions) + .skippedActions(skippedActions) + .msgsCreated(msgsCreated) + .actionListHash(actionListHash) + .totalMsgSizeCells(totalMsgSizeCells) + .totalMsgSizeBits(totalMsgSizeBits) + .build(); + } + } + + private static TransactionCredit parseTransactionCredit(String creditStr) { + if (StringUtils.isEmpty(creditStr)) { + return TransactionCredit.builder().build(); + } else { + BigDecimal creditDueFeesCollected = + parseBigDecimalBracket(sbb(creditStr, "due_fees_collected:("), VALUE_COLON); + Value credit = readValue(sbb(creditStr, "due_fees_collected")); + return TransactionCredit.builder() + .credit(credit) + .dueFeesCollected(creditDueFeesCollected) + .build(); + } + } + + private static TransactionStorage parseTransactionStorage(String storageStr) { + if (StringUtils.isEmpty(storageStr)) { + return TransactionStorage.builder().build(); + } else { + BigDecimal storageFeesCollected = + parseBigDecimalBracket(sbb(storageStr, "(tr_phase_storage"), VALUE_COLON); + BigDecimal dueFees = + parseBigDecimalBracket(sbb(storageStr, "storage_fees_due:("), VALUE_COLON); + String storageSccountStatus = sb(storageStr, "status_change:", CLOSE); + return TransactionStorage.builder() + .feesCollected(storageFeesCollected) + .feesDue(dueFees) + .statusChange(storageSccountStatus) + .build(); + } + } + + private static TransactionCompute parseTransactionCompute(String computeStr) { + if (StringUtils.isEmpty(computeStr)) { + return TransactionCompute.builder().build(); + } else { + BigDecimal gasFees = parseBigDecimalBracket(sbb(computeStr, "gas_fees:("), VALUE_COLON); + Byte success = parseByteSpace(computeStr, SUCCESS_COLON); + Byte msgStateUsed = parseByteSpace(computeStr, "msg_state_used:"); + Byte accountActivated = parseByteSpace(computeStr, "account_activated:"); + BigDecimal gasUsed = parseBigDecimalBracket(sbb(computeStr, "gas_used:("), VALUE_COLON); + BigDecimal gasLimit = parseBigDecimalBracket(sbb(computeStr, "gas_limit:("), VALUE_COLON); + BigDecimal gasCredit = + parseBigDecimalBracket( + sbb(computeStr, "gas_credit:("), + VALUE_COLON); // TODO parse gas_credit:(just value:(var_uint len:2 + // value:10000)) + String exitArgs = sb(computeStr, "exit_arg:", SPACE); + BigInteger exitCode = parseBigIntegerSpace(computeStr, "exit_code:"); + BigInteger mode = parseBigIntegerSpace(computeStr, "mode:"); + BigInteger vmsSteps = parseBigIntegerSpace(computeStr, "vm_steps:"); + String vmInitStateHash = sb(computeStr, "vm_init_state_hash:", SPACE); + if (Strings.isNotEmpty(vmInitStateHash)) { + vmInitStateHash = vmInitStateHash.substring(1); + } + String vmFinalStateHash = sb(computeStr, "vm_final_state_hash:", CLOSE); + if (Strings.isNotEmpty(vmFinalStateHash)) { + vmFinalStateHash = vmFinalStateHash.substring(1); + } + return TransactionCompute.builder() + .gasFees(gasFees) + .gasCredit(gasCredit) + .gasUsed(gasUsed) + .gasLimit(gasLimit) + .accountActivated(accountActivated) + .msgStateUsed(msgStateUsed) + .success(success) + .mode(mode) + .vmSteps(vmsSteps) + .vmInitStateHash(vmInitStateHash) + .vmFinalStateHash(vmFinalStateHash) + .exitArg(exitArgs) + .exitCode(exitCode) + .build(); + } + } + + private static ValueFlow parseValueFlow(String valueFlw) { + + Value prevBlock = readValue(sbb(valueFlw, "from_prev_blk:(currencies")); + Value nextBlock = readValue(sbb(valueFlw, "to_next_blk:(currencies")); + Value imported = readValue(sbb(valueFlw, "imported:(currencies")); + Value exported = readValue(sbb(valueFlw, "exported:(currencies")); + Value feesCollected = readValue(sbb(valueFlw, "fees_collected:(currencies")); + Value feesImported = readValue(sbb(valueFlw, "fees_imported:(currencies")); + Value recovered = readValue(sbb(valueFlw, "recovered:(currencies")); + Value created = readValue(sbb(valueFlw, "created:(currencies")); + Value minted = readValue(sbb(valueFlw, "minted:(currencies")); + + return ValueFlow.builder() + .prevBlock(prevBlock) + .nextBlock(nextBlock) + .imported(imported) + .exported(exported) + .feesCollected(feesCollected) + .feesImported(feesImported) + .recovered(recovered) + .created(created) + .minted(minted) + .build(); + } + + private static Value readValue(String str) { + if (StringUtils.isEmpty(str)) return Value.builder().toncoins(BigDecimal.ZERO).build(); + + String res = sbb(str, "amount:("); + BigDecimal grams = parseBigDecimalBracket(res, VALUE_COLON); + + List otherCurrencies = new ArrayList<>(); + + if (str.contains("node:(hmn_fork")) { + // exist extra currencies + List currenciesLeft = findStringBlocks(str, "left:(hm_edge"); + List currenciesRight = findStringBlocks(str, "right:(hm_edge"); + List currencies = new ArrayList<>(); + currencies.addAll(currenciesLeft); + currencies.addAll(currenciesRight); + + if (!currencies.isEmpty()) { + for (String cur : currencies) { + Byte len = parseByteSpace(cur, "len:"); + String label = sb(cur, "s:", CLOSE); + if (StringUtils.isNotEmpty(label)) { + label = label.substring(1); + } + if (!isNull(len)) { + BigDecimal value = + parseBigDecimalBracket(cur.substring(cur.indexOf("var_uint")), VALUE_COLON); + + Currency currency = Currency.builder().label(label).len(len).value(value).build(); + otherCurrencies.add(currency); + } + } + } + } - List allLibraries = new ArrayList<>(); - - if (str.contains("node:(hmn_fork")) { - List librariesLeft = findStringBlocks(str, "left:(hm_edge"); - List librariesRight = findStringBlocks(str, "right:(hm_edge"); - List libraries = new ArrayList<>(); - libraries.addAll(librariesLeft); - libraries.addAll(librariesRight); - - if (!libraries.isEmpty()) { - for (String lib : libraries) { - String label = sb(lib, "s:", CLOSE); - if (StringUtils.isNotEmpty(label)) { - label = label.substring(1); - } - String type = sb(lib, "value:(", SPACE); - Long publicFlag = parseLongSpace(lib, "public:"); - - String raw = sbb(lib, "root:("); - String[] rawCells = isNull(raw) ? new String[0] : raw.split("x\\{"); - List rawLibrary = cleanCells(new ArrayList<>(Arrays.asList(rawCells))); - - Library library = Library.builder() - .label(label) - .type(type) - .publicFlag(publicFlag) - .rawData(rawLibrary) - .build(); - - allLibraries.add(library); - } - } - } + return Value.builder().toncoins(grams).otherCurrencies(otherCurrencies).build(); + } - return allLibraries; - } - - private static Info parseBlockInfo(String blockInf) { - - BigInteger version = parseBigIntegerSpace(blockInf, "version:"); - Byte notMaster = parseByteSpace(blockInf, "not_master:"); - BigInteger keyBlock = parseBigIntegerSpace(blockInf, "key_block:"); - BigInteger vertSeqnoIncr = parseBigIntegerSpace(blockInf, "vert_seqno_incr:"); - BigInteger vertSeqno = parseBigIntegerSpace(blockInf, "vert_seq_no:"); - BigInteger genValidatorListHashShort = parseBigIntegerSpace(blockInf, "gen_validator_list_hash_short:"); - BigInteger genCatchainSeqno = parseBigIntegerSpace(blockInf, "gen_catchain_seqno:"); - BigInteger minRefMcSeqno = parseBigIntegerSpace(blockInf, "min_ref_mc_seqno:"); - BigInteger prevKeyBlockSeqno = parseBigIntegerSpace(blockInf, "prev_key_block_seqno:"); - Byte wantSplit = parseByteSpace(blockInf, "want_split:"); - Byte afterSplit = parseByteSpace(blockInf, "after_split:"); - Byte beforeSplit = parseByteSpace(blockInf, "before_split:"); - Byte afterMerge = parseByteSpace(blockInf, "after_merge:"); - Byte wantMerge = parseByteSpace(blockInf, "want_merge:"); - BigInteger seqno = parseBigIntegerSpace(blockInf, SEQ_NO_COLON); - Long wc = parseLongSpace(blockInf, WORKCHAIN_ID_COLON); - BigInteger startLt = parseBigIntegerSpace(blockInf, START_LT_COLON); - BigInteger endLt = parseBigIntegerSpace(blockInf, END_LT_COLON); - Long genUtime = parseLongSpace(blockInf, "gen_utime:"); - String prev = sbb(blockInf, "prev:("); - Long prevSeqno = parseLongSpace(prev, SEQ_NO_COLON); - BigInteger prevEndLt = parseBigIntegerSpace(prev, END_LT_COLON); - String prevRootHash = sb(prev, "root_hash:", SPACE); - if (Strings.isNotEmpty(prevRootHash)) { - prevRootHash = prevRootHash.substring(1); - } - String prevFileHash = sb(prev, "file_hash:", CLOSE); - if (Strings.isNotEmpty(prevFileHash)) { - prevFileHash = prevFileHash.substring(1); - } - return Info.builder() - .version(version) - .wc(wc) - .notMaster(notMaster) - .keyBlock(keyBlock) - .vertSeqno(vertSeqno) - .vertSeqnoIncr(vertSeqnoIncr) - .getValidatorListHashShort(genValidatorListHashShort) - .getCatchainSeqno(genCatchainSeqno) - .minRefMcSeqno(minRefMcSeqno) - .prevKeyBlockSeqno(prevKeyBlockSeqno) - .wantSplit(wantSplit) - .wantMerge(wantMerge) - .afterMerge(afterMerge) - .afterSplit(afterSplit) - .beforeSplit(beforeSplit) - .seqNo(seqno) - .startLt(startLt) - .endLt(endLt) - .genUtime(genUtime) - .prevBlockSeqno(prevSeqno) - .prevEndLt(prevEndLt) - .prevRootHash(prevRootHash) - .prevFileHash(prevFileHash) - .build(); - } - - - private static BigDecimal parseBigDecimalSpace(String str, String from) { - try { - String result = sb(str, from, SPACE); - return isNull(result) ? null : new BigDecimal(result); - } catch (Exception e) { - return null; - } + private static List readLibraries(String str) { + if (StringUtils.isEmpty(str)) { + return Collections.singletonList(Library.builder().build()); } - private static BigInteger parseBigIntegerSpace(String str, String from) { - try { - String result = sb(str, from, SPACE); - return isNull(result) ? null : new BigInteger(result); - } catch (Exception e) { - return null; - } + List allLibraries = new ArrayList<>(); + + if (str.contains("node:(hmn_fork")) { + List librariesLeft = findStringBlocks(str, "left:(hm_edge"); + List librariesRight = findStringBlocks(str, "right:(hm_edge"); + List libraries = new ArrayList<>(); + libraries.addAll(librariesLeft); + libraries.addAll(librariesRight); + + if (!libraries.isEmpty()) { + for (String lib : libraries) { + String label = sb(lib, "s:", CLOSE); + if (StringUtils.isNotEmpty(label)) { + label = label.substring(1); + } + String type = sb(lib, "value:(", SPACE); + Long publicFlag = parseLongSpace(lib, "public:"); + + String raw = sbb(lib, "root:("); + String[] rawCells = isNull(raw) ? new String[0] : raw.split("x\\{"); + List rawLibrary = cleanCells(new ArrayList<>(Arrays.asList(rawCells))); + + Library library = + Library.builder() + .label(label) + .type(type) + .publicFlag(publicFlag) + .rawData(rawLibrary) + .build(); + + allLibraries.add(library); + } + } } - private static Long parseLongSpace(String str, String from) { - try { - String result = sb(str, from, SPACE); - return isNull(result) ? null : Long.valueOf(result); - } catch (Exception e) { - return null; - } + return allLibraries; + } + + private static Info parseBlockInfo(String blockInf) { + + BigInteger version = parseBigIntegerSpace(blockInf, "version:"); + Byte notMaster = parseByteSpace(blockInf, "not_master:"); + BigInteger keyBlock = parseBigIntegerSpace(blockInf, "key_block:"); + BigInteger vertSeqnoIncr = parseBigIntegerSpace(blockInf, "vert_seqno_incr:"); + BigInteger vertSeqno = parseBigIntegerSpace(blockInf, "vert_seq_no:"); + BigInteger genValidatorListHashShort = + parseBigIntegerSpace(blockInf, "gen_validator_list_hash_short:"); + BigInteger genCatchainSeqno = parseBigIntegerSpace(blockInf, "gen_catchain_seqno:"); + BigInteger minRefMcSeqno = parseBigIntegerSpace(blockInf, "min_ref_mc_seqno:"); + BigInteger prevKeyBlockSeqno = parseBigIntegerSpace(blockInf, "prev_key_block_seqno:"); + Byte wantSplit = parseByteSpace(blockInf, "want_split:"); + Byte afterSplit = parseByteSpace(blockInf, "after_split:"); + Byte beforeSplit = parseByteSpace(blockInf, "before_split:"); + Byte afterMerge = parseByteSpace(blockInf, "after_merge:"); + Byte wantMerge = parseByteSpace(blockInf, "want_merge:"); + BigInteger seqno = parseBigIntegerSpace(blockInf, SEQ_NO_COLON); + Long wc = parseLongSpace(blockInf, WORKCHAIN_ID_COLON); + BigInteger startLt = parseBigIntegerSpace(blockInf, START_LT_COLON); + BigInteger endLt = parseBigIntegerSpace(blockInf, END_LT_COLON); + Long genUtime = parseLongSpace(blockInf, "gen_utime:"); + String prev = sbb(blockInf, "prev:("); + Long prevSeqno = parseLongSpace(prev, SEQ_NO_COLON); + BigInteger prevEndLt = parseBigIntegerSpace(prev, END_LT_COLON); + String prevRootHash = sb(prev, "root_hash:", SPACE); + if (Strings.isNotEmpty(prevRootHash)) { + prevRootHash = prevRootHash.substring(1); } - - private static Long parseLongBracket(String str, String from) { - if (isNull(str)) { - return 0L; - } - try { - String result = sb(str, from, CLOSE); - if (StringUtils.isNotEmpty(result)) { - result = result.trim(); - } - return isNull(result) ? null : Long.valueOf(result); - } catch (Exception e) { - return 0L; - } + String prevFileHash = sb(prev, "file_hash:", CLOSE); + if (Strings.isNotEmpty(prevFileHash)) { + prevFileHash = prevFileHash.substring(1); } - - private static BigDecimal parseBigDecimalBracket(String str, String from) { - if (isNull(str)) { - return BigDecimal.ZERO; - } - try { - String result = sb(str, from, CLOSE); - if (StringUtils.isNotEmpty(result)) { - result = result.trim(); - } - return isNull(result) ? null : new BigDecimal(result); - } catch (Exception e) { - return BigDecimal.ZERO; - } + return Info.builder() + .version(version) + .wc(wc) + .notMaster(notMaster) + .keyBlock(keyBlock) + .vertSeqno(vertSeqno) + .vertSeqnoIncr(vertSeqnoIncr) + .getValidatorListHashShort(genValidatorListHashShort) + .getCatchainSeqno(genCatchainSeqno) + .minRefMcSeqno(minRefMcSeqno) + .prevKeyBlockSeqno(prevKeyBlockSeqno) + .wantSplit(wantSplit) + .wantMerge(wantMerge) + .afterMerge(afterMerge) + .afterSplit(afterSplit) + .beforeSplit(beforeSplit) + .seqNo(seqno) + .startLt(startLt) + .endLt(endLt) + .genUtime(genUtime) + .prevBlockSeqno(prevSeqno) + .prevEndLt(prevEndLt) + .prevRootHash(prevRootHash) + .prevFileHash(prevFileHash) + .build(); + } + + private static BigDecimal parseBigDecimalSpace(String str, String from) { + try { + String result = sb(str, from, SPACE); + return isNull(result) ? null : new BigDecimal(result); + } catch (Exception e) { + return null; } - - private static BigInteger parseBigIntegerBracket(String str, String from) { - if (isNull(str)) { - return BigInteger.ZERO; - } - try { - String result = sb(str, from, CLOSE); - if (StringUtils.isNotEmpty(result)) { - result = result.trim(); - } - return isNull(result) ? null : new BigInteger(result); - } catch (Exception e) { - return BigInteger.ZERO; - } + } + + private static BigInteger parseBigIntegerSpace(String str, String from) { + try { + String result = sb(str, from, SPACE); + return isNull(result) ? null : new BigInteger(result); + } catch (Exception e) { + return null; } - - private static Byte parseByteSpace(String str, String from) { - try { - String result = sb(str, from, SPACE); - return isNull(result) ? null : Byte.parseByte(result); - } catch (Exception e) { - return null; - } + } + + private static Long parseLongSpace(String str, String from) { + try { + String result = sb(str, from, SPACE); + return isNull(result) ? null : Long.valueOf(result); + } catch (Exception e) { + return null; } + } - private static Byte parseByteBracket(String str, String from) { - String result = sb(str, from, CLOSE); - return isNull(result) ? null : Byte.parseByte(result); + private static Long parseLongBracket(String str, String from) { + if (isNull(str)) { + return 0L; } + try { + String result = sb(str, from, CLOSE); + if (StringUtils.isNotEmpty(result)) { + result = result.trim(); + } + return isNull(result) ? null : Long.valueOf(result); + } catch (Exception e) { + return 0L; + } + } - public static LiteClientAccountState parseGetAccount(String stdout) { - - if (isNull(stdout)) { - return LiteClientAccountState.builder().build(); - } - - if (stdout.contains("account state is empty")) { - return LiteClientAccountState.builder().build(); - } - - try { - String accountState = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - - String addr = sbb(accountState, "addr:("); - String address = sb(addr, ADDRESS_COLON, CLOSE); - if (StringUtils.isNotEmpty(address)) { - address = address.substring(1).toUpperCase(); - } - Long wc = parseLongSpace(addr, WORKCHAIN_ID_COLON); - - String storageStat = sbb(accountState, "storage_stat:("); - String usedCellsStr = sbb(storageStat, "cells:("); - String usedBitsStr = sbb(storageStat, "bits:("); - String usedPublicCellsStr = sbb(storageStat, "public_cells:("); - - Long usedCells = parseLongBracket(usedCellsStr, VALUE_COLON); - Long usedBits = parseLongBracket(usedBitsStr, VALUE_COLON); - Long usedPublicCells = parseLongBracket(usedPublicCellsStr, VALUE_COLON); - BigDecimal lastPaid = parseBigDecimalSpace(storageStat, "last_paid:"); - - String storage = sbb(accountState, "storage:("); - BigDecimal storageLastTxLt = parseBigDecimalSpace(storage, "last_trans_lt:"); - Value storageBalanceValue = readValue(storage); - - String state = sbb(accountState, "state:("); - String stateAccountStatus; - - if (StringUtils.isNotEmpty(state)) { // state:(account_active - stateAccountStatus = sb(accountState, "state:(", SPACE); - } else { - stateAccountStatus = sb(accountState, "state:", CLOSE); - } - - if (stateAccountStatus.contains("account_active")) { - stateAccountStatus = "Active"; - } else if (stateAccountStatus.contains("account_uninit")) { - stateAccountStatus = "Uninitialized"; - } else { - stateAccountStatus = "Frozen"; - } - - String code = sbb(state, "code:("); - String[] codeCells = isNull(code) ? new String[0] : code.split("x\\{"); - List stateAccountCode = cleanCells(new ArrayList<>(Arrays.asList(codeCells))); - - String data = sbb(state, "data:("); - String[] dataCells = isNull(data) ? new String[0] : data.split("x\\{"); - List stateAccountData = cleanCells(new ArrayList<>(Arrays.asList(dataCells))); - - String stateAccountLibrary = sbb(state, "library:("); - List libraries = readLibraries(stateAccountLibrary); - - BigInteger lastTxLt = parseBigIntegerSpace(accountState, "last transaction lt = "); - String lastTxHash = sb(accountState, "hash = ", SPACE); - StorageInfo storageInfo = StorageInfo.builder() - .usedCells(usedCells) - .usedBits(usedBits) - .usedPublicCells(usedPublicCells) - .lastPaid(lastPaid) - .build(); - - return LiteClientAccountState.builder() - .wc(wc) - .address(address) - .balance(storageBalanceValue) - .storageInfo(storageInfo) - .storageLastTxLt(storageLastTxLt) - .status(stateAccountStatus) - .stateCode(String.join("", stateAccountCode)) - .stateData(String.join("", stateAccountData)) - .stateLibrary(libraries) - .lastTxLt(lastTxLt) - .lastTxHash(lastTxHash) - .build(); - - } catch (Exception e) { - return LiteClientAccountState.builder().build(); - } + private static BigDecimal parseBigDecimalBracket(String str, String from) { + if (isNull(str)) { + return BigDecimal.ZERO; } + try { + String result = sb(str, from, CLOSE); + if (StringUtils.isNotEmpty(result)) { + result = result.trim(); + } + return isNull(result) ? null : new BigDecimal(result); + } catch (Exception e) { + return BigDecimal.ZERO; + } + } - public static long parseRunMethodSeqno(String stdout) { - try { - return Long.parseLong(sb(stdout, "result: [", "]").trim()); - } catch (Exception e) { - return -1L; - } + private static BigInteger parseBigIntegerBracket(String str, String from) { + if (isNull(str)) { + return BigInteger.ZERO; + } + try { + String result = sb(str, from, CLOSE); + if (StringUtils.isNotEmpty(result)) { + result = result.trim(); + } + return isNull(result) ? null : new BigInteger(result); + } catch (Exception e) { + return BigInteger.ZERO; } + } + + private static Byte parseByteSpace(String str, String from) { + try { + String result = sb(str, from, SPACE); + return isNull(result) ? null : Byte.parseByte(result); + } catch (Exception e) { + return null; + } + } - public static List parseRunMethodParticipantList(String stdout) { + private static Byte parseByteBracket(String str, String from) { + String result = sb(str, from, CLOSE); + return isNull(result) ? null : Byte.parseByte(result); + } - if (StringUtils.isEmpty(stdout) || !stdout.contains("participant_list")) - return Collections.emptyList(); + public static LiteClientAccountState parseGetAccount(String stdout) { - if (stdout.contains("cannot parse answer")) { - return Collections.emptyList(); - } + if (isNull(stdout)) { + return LiteClientAccountState.builder().build(); + } - String result = sb(stdout, "result: [ (", ") ]"); + if (stdout.contains("account state is empty")) { + return LiteClientAccountState.builder().build(); + } - result = result.replace("] [", ","); - result = result.replace("[", ""); - result = result.replace("]", ""); - String[] participants = result.split(","); + try { + String accountState = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + + String addr = sbb(accountState, "addr:("); + String address = sb(addr, ADDRESS_COLON, CLOSE); + if (StringUtils.isNotEmpty(address)) { + address = address.substring(1).toUpperCase(); + } + Long wc = parseLongSpace(addr, WORKCHAIN_ID_COLON); + + String storageStat = sbb(accountState, "storage_stat:("); + String usedCellsStr = sbb(storageStat, "cells:("); + String usedBitsStr = sbb(storageStat, "bits:("); + String usedPublicCellsStr = sbb(storageStat, "public_cells:("); + + Long usedCells = parseLongBracket(usedCellsStr, VALUE_COLON); + Long usedBits = parseLongBracket(usedBitsStr, VALUE_COLON); + Long usedPublicCells = parseLongBracket(usedPublicCellsStr, VALUE_COLON); + BigDecimal lastPaid = parseBigDecimalSpace(storageStat, "last_paid:"); + + String storage = sbb(accountState, "storage:("); + BigDecimal storageLastTxLt = parseBigDecimalSpace(storage, "last_trans_lt:"); + Value storageBalanceValue = readValue(storage); + + String state = sbb(accountState, "state:("); + String stateAccountStatus; + + if (StringUtils.isNotEmpty(state)) { // state:(account_active + stateAccountStatus = sb(accountState, "state:(", SPACE); + } else { + stateAccountStatus = sb(accountState, "state:", CLOSE); + } + + if (stateAccountStatus.contains("account_active")) { + stateAccountStatus = "Active"; + } else if (stateAccountStatus.contains("account_uninit")) { + stateAccountStatus = "Uninitialized"; + } else { + stateAccountStatus = "Frozen"; + } + + String code = sbb(state, "code:("); + String[] codeCells = isNull(code) ? new String[0] : code.split("x\\{"); + List stateAccountCode = cleanCells(new ArrayList<>(Arrays.asList(codeCells))); + + String data = sbb(state, "data:("); + String[] dataCells = isNull(data) ? new String[0] : data.split("x\\{"); + List stateAccountData = cleanCells(new ArrayList<>(Arrays.asList(dataCells))); + + String stateAccountLibrary = sbb(state, "library:("); + List libraries = readLibraries(stateAccountLibrary); + + BigInteger lastTxLt = parseBigIntegerSpace(accountState, "last transaction lt = "); + String lastTxHash = sb(accountState, "hash = ", SPACE); + StorageInfo storageInfo = + StorageInfo.builder() + .usedCells(usedCells) + .usedBits(usedBits) + .usedPublicCells(usedPublicCells) + .lastPaid(lastPaid) + .build(); + + return LiteClientAccountState.builder() + .wc(wc) + .address(address) + .balance(storageBalanceValue) + .storageInfo(storageInfo) + .storageLastTxLt(storageLastTxLt) + .status(stateAccountStatus) + .stateCode(String.join("", stateAccountCode)) + .stateData(String.join("", stateAccountData)) + .stateLibrary(libraries) + .lastTxLt(lastTxLt) + .lastTxHash(lastTxHash) + .build(); + + } catch (Exception e) { + return LiteClientAccountState.builder().build(); + } + } - List participantsList = new ArrayList<>(); - if (result.isEmpty()) { - return participantsList; - } - for (String participant : participants) { - String[] entry = participant.split(" "); - participantsList.add(ResultListParticipants.builder().pubkey(entry[0]).weight(entry[1]).build()); - } - return participantsList; + public static long parseRunMethodSeqno(String stdout) { + try { + return Long.parseLong(sb(stdout, "result: [", "]").trim()); + } catch (Exception e) { + return -1L; } + } - public static ResultComputeReturnStake parseRunMethodComputeReturnStake(String stdout) { + public static List parseRunMethodParticipantList(String stdout) { - log.info("parseRunMethodComputeReturnStake " + stdout); + if (StringUtils.isEmpty(stdout) || !stdout.contains("participant_list")) + return Collections.emptyList(); - if (StringUtils.isEmpty(stdout) || !stdout.contains("compute_returned_stake")) - return ResultComputeReturnStake.builder() - .stake(new BigDecimal("-1")) - .build(); + if (stdout.contains("cannot parse answer")) { + return Collections.emptyList(); + } - if (stdout.contains("cannot parse answer")) { - return ResultComputeReturnStake.builder() - .stake(new BigDecimal("-1")) - .build(); - } + String result = sb(stdout, "result: [ (", ") ]"); - String result = sb(stdout, "result: [", "]"); + result = result.replace("] [", ","); + result = result.replace("[", ""); + result = result.replace("]", ""); + String[] participants = result.split(","); - return ResultComputeReturnStake.builder() - .stake(new BigDecimal(result.trim())) - .build(); + List participantsList = new ArrayList<>(); + if (result.isEmpty()) { + return participantsList; + } + for (String participant : participants) { + String[] entry = participant.split(" "); + participantsList.add( + ResultListParticipants.builder().pubkey(entry[0]).weight(entry[1]).build()); } + return participantsList; + } - private static String sb(String str, String from, String to) { - if (str == null || from == null || to == null) { - return null; - } + public static ResultComputeReturnStake parseRunMethodComputeReturnStake(String stdout) { - byte[] bytes = str.getBytes(StandardCharsets.UTF_8); - byte[] fromBytes = from.getBytes(StandardCharsets.UTF_8); - byte[] toBytes = to.getBytes(StandardCharsets.UTF_8); + log.info("parseRunMethodComputeReturnStake {}", stdout); - int startIndex = indexOf(bytes, fromBytes, 0); - if (startIndex == -1) { - return null; - } - startIndex += fromBytes.length; + if (StringUtils.isEmpty(stdout) || !stdout.contains("compute_returned_stake")) + return ResultComputeReturnStake.builder().stake(new BigDecimal("-1")).build(); - int endIndex = indexOf(bytes, toBytes, startIndex); - if (endIndex == -1) { - return null; - } - - byte[] resultBytes = Arrays.copyOfRange(bytes, startIndex, endIndex); - return new String(resultBytes, StandardCharsets.UTF_8); + if (stdout.contains("cannot parse answer")) { + return ResultComputeReturnStake.builder().stake(new BigDecimal("-1")).build(); } - /** - * Finds single string-block starting with pattern and ending with CLOSE - */ - private static String sbb(String str, String pattern) { - if (str == null || pattern == null || !str.contains(pattern)) { - return null; - } + String result = sb(stdout, "result: [", "]"); - byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); - byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); + return ResultComputeReturnStake.builder().stake(new BigDecimal(result.trim())).build(); + } - int patternIndex = indexOf(strBytes, patternBytes, 0); - int patternOpenIndex = indexOf(patternBytes, OPEN.getBytes(StandardCharsets.UTF_8), 0); - if (patternIndex == -1) { - return null; - } + private static String sb(String str, String from, String to) { + if (str == null || from == null || to == null) { + return null; + } - int openIndex = patternIndex + patternOpenIndex; - int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + byte[] fromBytes = from.getBytes(StandardCharsets.UTF_8); + byte[] toBytes = to.getBytes(StandardCharsets.UTF_8); - if (closeIndex == -1) { - return null; - } + int startIndex = indexOf(bytes, fromBytes, 0); + if (startIndex == -1) { + return null; + } + startIndex += fromBytes.length; - return new String(strBytes, openIndex, closeIndex - openIndex + 1, StandardCharsets.UTF_8).trim(); - } - - /** - * Finds matching closing bracket in string starting with pattern - */ - private static int findPosOfClosingBracket(byte[] strBytes, int patternIndex) { - Deque stack = new ArrayDeque<>(); - - for (int i = patternIndex; i < strBytes.length; i++) { - if (strBytes[i] == (byte) '(') { - stack.push(i); - } else if (strBytes[i] == (byte) ')') { - if (stack.isEmpty()) { - return i; - } - stack.pop(); - if (stack.isEmpty()) { - return i; - } - } - } - return -1; - } - - /** - * Finds multiple string-blocks starting with pattern and ending with CLOSE - */ - private static List findStringBlocks(String str, String pattern) { - List result = new ArrayList<>(); - - if (isNull(str) || !str.contains(pattern)) - return result; - - byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); - byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); - - int fromIndex = 0; - int patternIndex = indexOf(strBytes, patternBytes, fromIndex); - - while (patternIndex != -1) { - byte[] tmp = Arrays.copyOfRange(strBytes, patternIndex, patternIndex + patternBytes.length); - int openCharIndex = indexOf(tmp, OPEN.getBytes(StandardCharsets.UTF_8), 0); - int openIndex = patternIndex + openCharIndex; - if (openIndex == -1) { - break; - } - int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); - - if (closeIndex != -1) { - result.add(new String(Arrays.copyOfRange(strBytes, openIndex, closeIndex + 1))); - fromIndex = closeIndex + 1; - patternIndex = indexOf(strBytes, patternBytes, fromIndex); - } else { - break; - } - } - return result; + int endIndex = indexOf(bytes, toBytes, startIndex); + if (endIndex == -1) { + return null; } - /** - * copy of the method findStringBlocks() but additionally prepends result with label value - */ - private static List findLeafsWithLabel(String str, String pattern) { - List result = new ArrayList<>(); + byte[] resultBytes = Arrays.copyOfRange(bytes, startIndex, endIndex); + return new String(resultBytes, StandardCharsets.UTF_8); + } - if (isNull(str) || !str.contains(pattern)) - return result; + /** Finds single string-block starting with pattern and ending with CLOSE */ + private static String sbb(String str, String pattern) { + if (str == null || pattern == null || !str.contains(pattern)) { + return null; + } - byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); - byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); + int patternIndex = indexOf(strBytes, patternBytes, 0); + int patternOpenIndex = indexOf(patternBytes, OPEN.getBytes(StandardCharsets.UTF_8), 0); + if (patternIndex == -1) { + return null; + } - int fromIndex = 0; - final int dataToAddLen = 150; - int patternIndex = indexOf(strBytes, patternBytes, fromIndex); + int openIndex = patternIndex + patternOpenIndex; + int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); - while (patternIndex != -1) { - byte[] tmp = Arrays.copyOfRange(strBytes, patternIndex, patternIndex + pattern.length()); - int openCharIndex = indexOf(tmp, OPEN.getBytes(StandardCharsets.UTF_8), 0); - int openIndex = patternIndex + openCharIndex; + if (closeIndex == -1) { + return null; + } - if (openIndex == -1) { - break; - } - int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); + return new String(strBytes, openIndex, closeIndex - openIndex + 1, StandardCharsets.UTF_8) + .trim(); + } - if (closeIndex != -1) { - int start = Math.max(patternIndex - dataToAddLen, 0); - byte[] beforePattern = Arrays.copyOfRange(strBytes, start, patternIndex); - byte[] foundPattern = Arrays.copyOfRange(strBytes, patternIndex, closeIndex + 1); + /** Finds matching closing bracket in string starting with pattern */ + private static int findPosOfClosingBracket(byte[] strBytes, int patternIndex) { + Deque stack = new ArrayDeque<>(); - result.add(new String(beforePattern) + new String(foundPattern)); - fromIndex = closeIndex + 1; - patternIndex = indexOf(strBytes, patternBytes, fromIndex); - } else { - break; - } + for (int i = patternIndex; i < strBytes.length; i++) { + if (strBytes[i] == (byte) '(') { + stack.push(i); + } else if (strBytes[i] == (byte) ')') { + if (stack.isEmpty()) { + return i; } - return result; + stack.pop(); + if (stack.isEmpty()) { + return i; + } + } + } + return -1; + } + + /** Finds multiple string-blocks starting with pattern and ending with CLOSE */ + private static List findStringBlocks(String str, String pattern) { + List result = new ArrayList<>(); + + if (isNull(str) || !str.contains(pattern)) return result; + + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); + + int fromIndex = 0; + int patternIndex = indexOf(strBytes, patternBytes, fromIndex); + + while (patternIndex != -1) { + byte[] tmp = Arrays.copyOfRange(strBytes, patternIndex, patternIndex + patternBytes.length); + int openCharIndex = indexOf(tmp, OPEN.getBytes(StandardCharsets.UTF_8), 0); + int openIndex = patternIndex + openCharIndex; + if (openIndex == -1) { + break; + } + int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); + + if (closeIndex != -1) { + result.add(new String(Arrays.copyOfRange(strBytes, openIndex, closeIndex + 1))); + fromIndex = closeIndex + 1; + patternIndex = indexOf(strBytes, patternBytes, fromIndex); + } else { + break; + } + } + return result; + } + + /** copy of the method findStringBlocks() but additionally prepends result with label value */ + private static List findLeafsWithLabel(String str, String pattern) { + List result = new ArrayList<>(); + + if (isNull(str) || !str.contains(pattern)) return result; + + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); + + int fromIndex = 0; + final int dataToAddLen = 150; + int patternIndex = indexOf(strBytes, patternBytes, fromIndex); + + while (patternIndex != -1) { + byte[] tmp = Arrays.copyOfRange(strBytes, patternIndex, patternIndex + pattern.length()); + int openCharIndex = indexOf(tmp, OPEN.getBytes(StandardCharsets.UTF_8), 0); + int openIndex = patternIndex + openCharIndex; + + if (openIndex == -1) { + break; + } + int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); + + if (closeIndex != -1) { + int start = Math.max(patternIndex - dataToAddLen, 0); + byte[] beforePattern = Arrays.copyOfRange(strBytes, start, patternIndex); + byte[] foundPattern = Arrays.copyOfRange(strBytes, patternIndex, closeIndex + 1); + + result.add(new String(beforePattern) + new String(foundPattern)); + fromIndex = closeIndex + 1; + patternIndex = indexOf(strBytes, patternBytes, fromIndex); + } else { + break; + } } + return result; + } - private static int indexOf(byte[] array, byte[] target, int fromIndex) { - if (target.length == 0) { - return fromIndex; - } - if (target.length > array.length) { - return -1; - } + private static int indexOf(byte[] array, byte[] target, int fromIndex) { + if (target.length == 0) { + return fromIndex; + } + if (target.length > array.length) { + return -1; + } - int[] a = new int[256]; - for (int i = 0; i < target.length; i++) { - a[target[i] & 0xFF] = i; - } + int[] a = new int[256]; + for (int i = 0; i < target.length; i++) { + a[target[i] & 0xFF] = i; + } - int m = target.length; - int n = array.length; - - int s = fromIndex; - while (s <= n - m) { - int j = m - 1; - while (j >= 0 && target[j] == array[s + j]) { - j--; - } - if (j < 0) { - return s; - } else { - s += Math.max(1, j - a[array[s + j] & 0xFF]); - } - } - return -1; + int m = target.length; + int n = array.length; + + int s = fromIndex; + while (s <= n - m) { + int j = m - 1; + while (j >= 0 && target[j] == array[s + j]) { + j--; + } + if (j < 0) { + return s; + } else { + s += Math.max(1, j - a[array[s + j] & 0xFF]); + } } + return -1; + } } diff --git a/liteclient/src/test/java/org/ton/java/liteclient/LiteClientTest.java b/liteclient/src/test/java/org/ton/java/liteclient/LiteClientTest.java index fed52606..aa411302 100644 --- a/liteclient/src/test/java/org/ton/java/liteclient/LiteClientTest.java +++ b/liteclient/src/test/java/org/ton/java/liteclient/LiteClientTest.java @@ -1,5 +1,14 @@ package org.ton.java.liteclient; +import static java.util.Objects.isNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatObject; +import static org.junit.Assert.*; + +import java.io.File; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -10,288 +19,311 @@ import org.ton.java.liteclient.api.block.Block; import org.ton.java.liteclient.api.block.Transaction; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.util.List; - -import static java.util.Objects.isNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatObject; -import static org.junit.Assert.*; - -/** - * Integration Tests that run against live testnet - */ +/** Integration Tests that run against live testnet */ @Slf4j public class LiteClientTest { - private static final String CURRENT_DIR = System.getProperty("user.dir"); - - private static LiteClient liteClient; - - @BeforeClass - public static void executedBeforeEach() throws IOException { - InputStream liteClientStream = LiteClient.class.getClassLoader().getResourceAsStream("lite-client.exe"); - File f = new File("lite-client.exe"); - FileUtils.copyInputStreamToFile(liteClientStream, f); - String pathToLiteClient = f.getAbsolutePath(); - liteClientStream.close(); - - liteClient = LiteClient.builder() - .pathToLiteClientBinary(pathToLiteClient) - .testnet(true) - .build(); + private static final String CURRENT_DIR = System.getProperty("user.dir"); + + private static LiteClient liteClient; + + @BeforeClass + public static void executedBeforeEach() { + + liteClient = LiteClient.builder().testnet(true).build(); + } + + @Test + public void testLastExecuted() { + assertThat(liteClient.executeLast()) + .isNotNull() + .contains("last masterchain block is") + .contains("server time is"); + } + + @Test + public void testRunMethod() throws Exception { + final String result = + liteClient.executeRunMethod( + "EQDCJVrezD71y-KPcTIG-YeKNj4naeiR7odpQgVA1uDsZqPC", "seqno", ""); + log.info(result); + assertThat(result).contains("arguments").contains("result"); + } + + @Test + public void testRunMethodWithBlockId() throws Exception { + final String result = + liteClient.executeRunMethod( + "EQDCJVrezD71y-KPcTIG-YeKNj4naeiR7odpQgVA1uDsZqPC", + "(-1,8000000000000000,20301499):070D07EB64D36CCA2D8D20AA644489637059C150E2CD466247C25B4997FB8CD9:D7D7271D466D52D0A98771F9E8DCAA06E43FCE01C977AACD9DE9DAD9A9F9A424", + "seqno", + ""); + log.info(result); + assertThat(result).contains("arguments").contains("result"); + } + + @Test + public void testRunMethodWithResultBlockId() throws Exception { + ResultLastBlock blockId = + ResultLastBlock.builder() + .wc(-1L) + .shard("8000000000000000") + .seqno(BigInteger.valueOf(20301499)) + .rootHash("070D07EB64D36CCA2D8D20AA644489637059C150E2CD466247C25B4997FB8CD9") + .fileHash("D7D7271D466D52D0A98771F9E8DCAA06E43FCE01C977AACD9DE9DAD9A9F9A424") + .build(); + final String result = + liteClient.executeRunMethod( + "EQDCJVrezD71y-KPcTIG-YeKNj4naeiR7odpQgVA1uDsZqPC", blockId, "seqno", ""); + log.info(result); + assertThat(result).contains("arguments").contains("result"); + } + + @Test + public void testSendfile() throws Exception { + final InputStream bocFile = + IOUtils.toBufferedInputStream(getClass().getResourceAsStream("/new-wallet.boc")); + final File targetFile = new File(CURRENT_DIR + File.separator + "new-wallet.boc"); + FileUtils.copyInputStreamToFile(bocFile, targetFile); + final String result = liteClient.executeSendfile(targetFile.getAbsolutePath()); + log.info(result); + assertThat(result).contains("sending query from file").contains("external message status is 1"); + } + + @Test + public void testListblocktransExecuted() { + // given + String resultLast = liteClient.executeLast(); + log.info("testListblocktransExecuted resultLast received"); + ResultLastBlock resultLastBlock = LiteClientParser.parseLast(resultLast); + log.info("testListblocktransExecuted tonBlockId {}", resultLastBlock); + // when + String stdout = liteClient.executeListblocktrans(resultLastBlock, 2000); + System.out.println(stdout); + // then + assertThat(stdout) + .isNotNull() + .contains("last masterchain block is") + .contains("obtained block") + .contains("transaction #") + .contains("account") + .contains("hash"); + } + + @Test + public void testAllShardsExecuted() throws Exception { + // given + String resultLast = liteClient.executeLast(); + log.info("testAllShardsExecuted resultLast received"); + assertThat(resultLast).isNotEmpty(); + ResultLastBlock resultLastBlock = LiteClientParser.parseLast(resultLast); + // when + String stdout = liteClient.executeAllshards(resultLastBlock); + // then + assertThat(stdout) + .isNotNull() + .contains("last masterchain block is") + .contains("obtained block") + .contains("got shard configuration with respect to block") + .contains("shard configuration is") + .contains("shard #"); + } + + @Test + public void testParseBySeqno() throws Exception { + // given + // 9MB size block + // (0,f880000000000000,4166691):6101667C299D3DD8C9E4C68F0BCEBDBA5473D812953C291DBF6D69198C34011B:608F5FC6D6CFB8D01A3D4A2F9EA5C353D82B4A08D7D755D8267D0141358329F1 + String resultLast = liteClient.executeLast(); + assertThat(resultLast).isNotEmpty(); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(resultLast); + assertThatObject(blockIdLast).isNotNull(); + assertNotNull(blockIdLast.getRootHash()); + // when + String stdout = + liteClient.executeBySeqno( + blockIdLast.getWc(), blockIdLast.getShard(), blockIdLast.getSeqno()); + log.info(stdout); + ResultLastBlock blockId = LiteClientParser.parseBySeqno(stdout); + // then + assertEquals(-1L, blockId.getWc().longValue()); + assertNotEquals(0L, blockId.getShard()); + assertNotEquals(0L, blockId.getSeqno().longValue()); + } + + @Test + public void testDumpBlockRealTimeExecuted() { + log.info( + "testDumpBlockRealTimeExecuted test executes against the most recent state of TON blockchain, if it fails means the return format has changed - react asap."); + // given + String resultLast = liteClient.executeLast(); + log.info("testDumpBlockRealTimeExecuted resultLast received"); + assertThat(resultLast).isNotEmpty(); + ResultLastBlock resultLastBlock = LiteClientParser.parseLast(resultLast); + log.info("testDumpBlockRealTimeExecuted tonBlockId {}", resultLastBlock); + + // when + String stdout = liteClient.executeDumpblock(resultLastBlock); + log.info(stdout); + // then + assertThat(stdout) + .isNotNull() + .contains("last masterchain block is") + .contains("got block download request for") + .contains("block header of") + .contains("block contents is (block global_id") + .contains("state_update:(raw@(MERKLE_UPDATE") + .contains("extra:(block_extra") + .contains("shard_fees:(") + .contains("x{11EF55AAFFFFFF"); + } + + @Test + public void testParseLastParsed() { + // given + String stdout = liteClient.executeLast(); + assertNotNull(stdout); + // when + ResultLastBlock blockId = LiteClientParser.parseLast(stdout); + // then + assertNotNull(blockId); + assertNotNull(blockId.getFileHash()); + assertNotNull(blockId.getRootHash()); + assertEquals(-1L, blockId.getWc().longValue()); + assertNotEquals(0L, blockId.getShard()); + assertNotEquals(0L, blockId.getSeqno().longValue()); + } + + @Test + public void testParseListBlockTrans() { + // given + String stdoutLast = liteClient.executeLast(); + // when + assertNotNull(stdoutLast); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); + + String stdoutListblocktrans = liteClient.executeListblocktrans(blockIdLast, 0); + log.info(stdoutListblocktrans); + // then + assertNotNull(stdoutListblocktrans); + List txs = + LiteClientParser.parseListBlockTrans(stdoutListblocktrans); + txs.forEach(System.out::println); + assertEquals(BigInteger.ONE, txs.get(0).getTxSeqno()); + } + + @Test + public void testParseAllShards() throws Exception { + // given + String stdoutLast = liteClient.executeLast(); + // when + assertNotNull(stdoutLast); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); + String stdoutAllShards = liteClient.executeAllshards(blockIdLast); + log.info(stdoutAllShards); + // then + assertNotNull(stdoutAllShards); + List shards = LiteClientParser.parseAllShards(stdoutAllShards); + + shards.forEach(System.out::println); + assertTrue(shards.get(0).getSeqno().longValue() > 0); + } + + @Test + public void testParseDumptransNew() { + // given + String stdoutLast = liteClient.executeLast(); + assertNotNull(stdoutLast); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); + + String stdoutListblocktrans = liteClient.executeListblocktrans(blockIdLast, 0); + assertNotNull(stdoutListblocktrans); + log.info(stdoutListblocktrans); + List txs = + LiteClientParser.parseListBlockTrans(stdoutListblocktrans); + + for (ResultListBlockTransactions tx : txs) { + String stdoutDumptrans = liteClient.executeDumptrans(blockIdLast, tx); + assertNotNull(stdoutDumptrans); + Transaction txdetails = LiteClientParser.parseDumpTrans(stdoutDumptrans, true); + if (!isNull(txdetails)) { + log.info(txdetails.toString()); + assertNotEquals(0, txdetails.getLt().longValue()); + } } - - @Test - public void testLastExecuted() { - assertThat(liteClient.executeLast()).isNotNull().contains("last masterchain block is").contains("server time is"); - } - - - @Test - public void testRunMethod() throws Exception { - final String result = liteClient.executeRunMethod("EQDCJVrezD71y-KPcTIG-YeKNj4naeiR7odpQgVA1uDsZqPC", "seqno", ""); - log.info(result); - assertThat(result).contains("arguments").contains("result"); - } - - @Test - public void testRunMethodWithBlockId() throws Exception { - final String result = liteClient.executeRunMethod("EQDCJVrezD71y-KPcTIG-YeKNj4naeiR7odpQgVA1uDsZqPC", - "(-1,8000000000000000,20301499):070D07EB64D36CCA2D8D20AA644489637059C150E2CD466247C25B4997FB8CD9:D7D7271D466D52D0A98771F9E8DCAA06E43FCE01C977AACD9DE9DAD9A9F9A424", - "seqno", ""); - log.info(result); - assertThat(result).contains("arguments").contains("result"); + } + + @Test + public void testParseAllSteps() throws Exception { + // given + String stdoutLast = liteClient.executeLast(); + assertNotNull(stdoutLast); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); + + String stdoutAllShards = liteClient.executeAllshards(blockIdLast); + // log.info(stdoutAllShards); + + String stdoutListblocktrans = liteClient.executeListblocktrans(blockIdLast, 0); + assertNotNull(stdoutListblocktrans); + log.info(stdoutListblocktrans); + List txs = + LiteClientParser.parseListBlockTrans(stdoutListblocktrans); + + // then + assertNotNull(stdoutAllShards); + List shards = LiteClientParser.parseAllShards(stdoutAllShards); + for (ResultLastBlock shard : shards) { + String stdoutListblocktrans2 = liteClient.executeListblocktrans(shard, 0); + List txs2 = + LiteClientParser.parseListBlockTrans(stdoutListblocktrans2); + txs.addAll(txs2); } - @Test - public void testRunMethodWithResultBlockId() throws Exception { - ResultLastBlock blockId = ResultLastBlock.builder() - .wc(-1L) - .shard("8000000000000000") - .seqno(BigInteger.valueOf(20301499)) - .rootHash("070D07EB64D36CCA2D8D20AA644489637059C150E2CD466247C25B4997FB8CD9") - .fileHash("D7D7271D466D52D0A98771F9E8DCAA06E43FCE01C977AACD9DE9DAD9A9F9A424") - .build(); - final String result = liteClient.executeRunMethod("EQDCJVrezD71y-KPcTIG-YeKNj4naeiR7odpQgVA1uDsZqPC", - blockId, - "seqno", ""); - log.info(result); - assertThat(result).contains("arguments").contains("result"); - } - - @Test - public void testSendfile() throws Exception { - final InputStream bocFile = IOUtils.toBufferedInputStream(getClass().getResourceAsStream("/new-wallet.boc")); - final File targetFile = new File(CURRENT_DIR + File.separator + "new-wallet.boc"); - FileUtils.copyInputStreamToFile(bocFile, targetFile); - final String result = liteClient.executeSendfile(targetFile.getAbsolutePath()); - log.info(result); - assertThat(result).contains("sending query from file").contains("external message status is 1"); - } - - @Test - public void testListblocktransExecuted() { - //given - String resultLast = liteClient.executeLast(); - log.info("testListblocktransExecuted resultLast received"); - ResultLastBlock resultLastBlock = LiteClientParser.parseLast(resultLast); - log.info("testListblocktransExecuted tonBlockId {}", resultLastBlock); - // when - String stdout = liteClient.executeListblocktrans(resultLastBlock, 2000); - System.out.println(stdout); - // then - assertThat(stdout).isNotNull().contains("last masterchain block is").contains("obtained block").contains("transaction #").contains("account").contains("hash"); - } - - @Test - public void testAllShardsExecuted() throws Exception { - //given - String resultLast = liteClient.executeLast(); - log.info("testAllShardsExecuted resultLast received"); - assertThat(resultLast).isNotEmpty(); - ResultLastBlock resultLastBlock = LiteClientParser.parseLast(resultLast); - // when - String stdout = liteClient.executeAllshards(resultLastBlock); - // then - assertThat(stdout).isNotNull() - .contains("last masterchain block is") - .contains("obtained block") - .contains("got shard configuration with respect to block") - .contains("shard configuration is").contains("shard #"); - } - - @Test - public void testParseBySeqno() throws Exception { - // given - // 9MB size block (0,f880000000000000,4166691):6101667C299D3DD8C9E4C68F0BCEBDBA5473D812953C291DBF6D69198C34011B:608F5FC6D6CFB8D01A3D4A2F9EA5C353D82B4A08D7D755D8267D0141358329F1 - String resultLast = liteClient.executeLast(); - assertThat(resultLast).isNotEmpty(); - ResultLastBlock blockIdLast = LiteClientParser.parseLast(resultLast); - assertThatObject(blockIdLast).isNotNull(); - assertNotNull(blockIdLast.getRootHash()); - // when - String stdout = liteClient.executeBySeqno(blockIdLast.getWc(), blockIdLast.getShard(), blockIdLast.getSeqno()); - log.info(stdout); - ResultLastBlock blockId = LiteClientParser.parseBySeqno(stdout); - // then - assertEquals(-1L, blockId.getWc().longValue()); - assertNotEquals(0L, blockId.getShard()); - assertNotEquals(0L, blockId.getSeqno().longValue()); - } - - @Test - public void testDumpBlockRealTimeExecuted() { - log.info("testDumpBlockRealTimeExecuted test executes against the most recent state of TON blockchain, if it fails means the return format has changed - react asap."); - //given - String resultLast = liteClient.executeLast(); - log.info("testDumpBlockRealTimeExecuted resultLast received"); - assertThat(resultLast).isNotEmpty(); - ResultLastBlock resultLastBlock = LiteClientParser.parseLast(resultLast); - log.info("testDumpBlockRealTimeExecuted tonBlockId {}", resultLastBlock); - - // when - String stdout = liteClient.executeDumpblock(resultLastBlock); - log.info(stdout); - // then - assertThat(stdout).isNotNull() - .contains("last masterchain block is").contains("got block download request for") - .contains("block header of") - .contains("block contents is (block global_id") - .contains("state_update:(raw@(MERKLE_UPDATE") - .contains("extra:(block_extra") - .contains("shard_fees:(") - .contains("x{11EF55AAFFFFFF"); - } - - @Test - public void testParseLastParsed() { - // given - String stdout = liteClient.executeLast(); - assertNotNull(stdout); - // when - ResultLastBlock blockId = LiteClientParser.parseLast(stdout); - // then - assertNotNull(blockId); - assertNotNull(blockId.getFileHash()); - assertNotNull(blockId.getRootHash()); - assertEquals(-1L, blockId.getWc().longValue()); - assertNotEquals(0L, blockId.getShard()); - assertNotEquals(0L, blockId.getSeqno().longValue()); - } - - @Test - public void testParseListBlockTrans() { - //given - String stdoutLast = liteClient.executeLast(); - // when - assertNotNull(stdoutLast); - ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); - - String stdoutListblocktrans = liteClient.executeListblocktrans(blockIdLast, 0); - log.info(stdoutListblocktrans); - //then - assertNotNull(stdoutListblocktrans); - List txs = LiteClientParser.parseListBlockTrans(stdoutListblocktrans); - txs.forEach(System.out::println); - assertEquals(BigInteger.ONE, txs.get(0).getTxSeqno()); - } - - @Test - public void testParseAllShards() throws Exception { - //given - String stdoutLast = liteClient.executeLast(); - // when - assertNotNull(stdoutLast); - ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); - String stdoutAllShards = liteClient.executeAllshards(blockIdLast); - log.info(stdoutAllShards); - //then - assertNotNull(stdoutAllShards); - List shards = LiteClientParser.parseAllShards(stdoutAllShards); - - shards.forEach(System.out::println); - assertTrue(shards.get(0).getSeqno().longValue() > 0); - } - - @Test - public void testParseDumptransNew() { - //given - String stdoutLast = liteClient.executeLast(); - assertNotNull(stdoutLast); - ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); - - String stdoutListblocktrans = liteClient.executeListblocktrans(blockIdLast, 0); - assertNotNull(stdoutListblocktrans); - log.info(stdoutListblocktrans); - List txs = LiteClientParser.parseListBlockTrans(stdoutListblocktrans); - - for (ResultListBlockTransactions tx : txs) { - String stdoutDumptrans = liteClient.executeDumptrans(blockIdLast, tx); - assertNotNull(stdoutDumptrans); - Transaction txdetails = LiteClientParser.parseDumpTrans(stdoutDumptrans, true); - if (!isNull(txdetails)) { - log.info(txdetails.toString()); - assertNotEquals(0, txdetails.getLt().longValue()); - } - } - } - - @Test - public void testParseAllSteps() throws Exception { - //given - String stdoutLast = liteClient.executeLast(); - assertNotNull(stdoutLast); - ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); - - String stdoutAllShards = liteClient.executeAllshards(blockIdLast); - //log.info(stdoutAllShards); - - String stdoutListblocktrans = liteClient.executeListblocktrans(blockIdLast, 0); - assertNotNull(stdoutListblocktrans); - log.info(stdoutListblocktrans); - List txs = LiteClientParser.parseListBlockTrans(stdoutListblocktrans); - - //then - assertNotNull(stdoutAllShards); - List shards = LiteClientParser.parseAllShards(stdoutAllShards); - for (ResultLastBlock shard : shards) { - String stdoutListblocktrans2 = liteClient.executeListblocktrans(shard, 0); - List txs2 = LiteClientParser.parseListBlockTrans(stdoutListblocktrans2); - txs.addAll(txs2); - } - - txs.forEach(System.out::println); - - for (ResultListBlockTransactions tx : txs) { - String stdoutDumptrans = liteClient.executeDumptrans(blockIdLast, tx); - assertNotNull(stdoutDumptrans); - Transaction txdetails = LiteClientParser.parseDumpTrans(stdoutDumptrans, true); - if (!isNull(txdetails)) { - assertNotEquals(0, txdetails.getLt().longValue()); - } - } - } - - @Test - public void testParseDumpblock() throws Exception { - //given - String stdoutLast = liteClient.executeLast(); - assertNotNull(stdoutLast); - ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); - String stdoutDumpblock = liteClient.executeDumpblock(blockIdLast); - - Block block = LiteClientParser.parseDumpblock(stdoutDumpblock, false, false); - assertNotNull(block); - - assertNotEquals(0L, block.getGlobalId().longValue()); - assertNotEquals(0L, block.getInfo().getSeqNo().longValue()); - assertNotNull(block.getInfo().getPrevFileHash()); - block.listBlockTrans().forEach(x -> log.info("account: {} lt: {} hash: {}", x.getAccountAddr(), x.getLt(), x.getNewHash())); + txs.forEach(System.out::println); - block.allShards().forEach(x -> log.info("wc: {} shard: {}, seqno: {} root_hash: {} file_hash: {} utime: {}, start_lt: {} end_lt: {}", - x.getWc(), new BigInteger(x.getNextValidatorShard()).toString(16), x.getSeqno(), x.getRootHash(), x.getFileHash(), x.getGenUtime(), x.getStartLt(), x.getEndLt())); + for (ResultListBlockTransactions tx : txs) { + String stdoutDumptrans = liteClient.executeDumptrans(blockIdLast, tx); + assertNotNull(stdoutDumptrans); + Transaction txdetails = LiteClientParser.parseDumpTrans(stdoutDumptrans, true); + if (!isNull(txdetails)) { + assertNotEquals(0, txdetails.getLt().longValue()); + } } + } + + @Test + public void testParseDumpblock() throws Exception { + // given + String stdoutLast = liteClient.executeLast(); + assertNotNull(stdoutLast); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); + String stdoutDumpblock = liteClient.executeDumpblock(blockIdLast); + + Block block = LiteClientParser.parseDumpblock(stdoutDumpblock, false, false); + assertNotNull(block); + + assertNotEquals(0L, block.getGlobalId().longValue()); + assertNotEquals(0L, block.getInfo().getSeqNo().longValue()); + assertNotNull(block.getInfo().getPrevFileHash()); + block + .listBlockTrans() + .forEach( + x -> + log.info( + "account: {} lt: {} hash: {}", x.getAccountAddr(), x.getLt(), x.getNewHash())); + + block + .allShards() + .forEach( + x -> + log.info( + "wc: {} shard: {}, seqno: {} root_hash: {} file_hash: {} utime: {}, start_lt: {} end_lt: {}", + x.getWc(), + new BigInteger(x.getNextValidatorShard()).toString(16), + x.getSeqno(), + x.getRootHash(), + x.getFileHash(), + x.getGenUtime(), + x.getStartLt(), + x.getEndLt())); + } } diff --git a/pom.xml b/pom.xml index 5200afa6..3ea29b3a 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,6 @@ ch.qos.logback logback-classic ${logback.version} - test org.assertj diff --git a/smartcontract/pom.xml b/smartcontract/pom.xml index 4b76fb6e..f8242b46 100644 --- a/smartcontract/pom.xml +++ b/smartcontract/pom.xml @@ -114,7 +114,6 @@ ch.qos.logback logback-classic - test org.assertj diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/GenericSmartContract.java b/smartcontract/src/main/java/org/ton/java/smartcontract/GenericSmartContract.java index 1922848f..bb7a4123 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/GenericSmartContract.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/GenericSmartContract.java @@ -70,6 +70,17 @@ public ExtMessageInfo deploy(Cell deployMessageBody) { return tonlib.sendRawMessage(prepareDeployMsg(deployMessageBody).toCell().toBase64()); } + /** + * Deploy with body without signing it. + * + * @param deployMessageBody usually stands for internal message + * @return ExtMessageInfo + */ + public ExtMessageInfo deployWithoutSignature(Cell deployMessageBody) { + return tonlib.sendRawMessage( + prepareDeployMsgWithoutSignature(deployMessageBody).toCell().toBase64()); + } + /** * Deploy without body * @@ -100,4 +111,13 @@ public Message prepareDeployMsg(Cell deployMessageBody) { .endCell()) .build(); } + + public Message prepareDeployMsgWithoutSignature(Cell deployMessageBody) { + + return Message.builder() + .info(ExternalMessageInInfo.builder().dstAddr(getAddressIntStd()).build()) + .init(getStateInit()) + .body(deployMessageBody) + .build(); + } } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java b/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java index 21bf2f82..c275106c 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java @@ -6,7 +6,7 @@ import jdk.nashorn.internal.ir.annotations.Ignore; import lombok.Builder; import lombok.Getter; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.cell.Cell; import org.ton.java.fift.FiftRunner; @@ -18,7 +18,7 @@ */ @Builder @Getter -@Log +@Slf4j public class SmartContractCompiler { String contractPath; @@ -28,6 +28,8 @@ public class SmartContractCompiler { @Ignore private FuncRunner funcRunner; + private boolean printFiftAsmOutput; + public static class SmartContractCompilerBuilder {} public static SmartContractCompilerBuilder builder() { @@ -74,6 +76,9 @@ public String compile() { .replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "") .replaceAll("\n", " ") .replaceAll("\r", " "); + if (printFiftAsmOutput) { + System.out.println(outputFiftAsmFile); + } return fiftRunner.runStdIn(new File(contractPath).getParent(), outputFiftAsmFile); } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/multisig/MultiSigWallet.java b/smartcontract/src/main/java/org/ton/java/smartcontract/multisig/MultiSigWallet.java index 5fa0c44b..2b36ba5e 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/multisig/MultiSigWallet.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/multisig/MultiSigWallet.java @@ -8,7 +8,7 @@ import java.util.*; import lombok.Builder; import lombok.Getter; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.address.Address; import org.ton.java.cell.*; @@ -21,728 +21,727 @@ @Builder @Getter -@Log +@Slf4j public class MultiSigWallet implements Contract { - //https://github.com/akifoq/multisig/blob/master/multisig-code.fc - TweetNaclFast.Signature.KeyPair keyPair; - long walletId; - MultiSigConfig config; + // https://github.com/akifoq/multisig/blob/master/multisig-code.fc + TweetNaclFast.Signature.KeyPair keyPair; + long walletId; + MultiSigConfig config; - /** - * interface to multisig wallet smart-contract - *

- * mandatory - highloadQueryId, walletId, publicKey - */ - - public static class MultiSigWalletBuilder { - } - - public static MultiSigWalletBuilder builder() { - return new CustomMultiSigWalletBuilder(); - } - - private static class CustomMultiSigWalletBuilder extends MultiSigWalletBuilder { - @Override - public MultiSigWallet build() { - if (isNull(super.keyPair)) { - super.keyPair = Utils.generateSignatureKeyPair(); - } - return super.build(); - } - } - - private Tonlib tonlib; - private long wc; - - @Override - public Tonlib getTonlib() { - return tonlib; - } - - @Override - public long getWorkchain() { - return wc; - } - - @Override - public String getName() { - return "multisig"; - } + /** + * interface to multisig + * wallet smart-contract + * + *

mandatory - highloadQueryId, walletId, publicKey + */ + public static class MultiSigWalletBuilder {} + public static MultiSigWalletBuilder builder() { + return new CustomMultiSigWalletBuilder(); + } - /** - * Initial contract storage (init state). - * Creator/deployer will be always part of k signatures. - * By default, it will reside in owner_infos dict at index 0. - * - * @return Cell - */ + private static class CustomMultiSigWalletBuilder extends MultiSigWalletBuilder { @Override - public Cell createDataCell() { - CellBuilder cell = CellBuilder.beginCell(); - - cell.storeUint(walletId, 32); - cell.storeUint(config.getN(), 8); // n - cell.storeUint(config.getK(), 8); // k - collect at least k signatures - cell.storeUint(0, 64); // last cleaned - if (isNull(config.getOwners()) || config.getOwners().isEmpty()) { - cell.storeBit(false); // initial owners dict - } else { - cell.storeDict(createOwnersInfoDict(config.getOwners())); + public MultiSigWallet build() { + if (isNull(super.keyPair)) { + super.keyPair = Utils.generateSignatureKeyPair(); + } + return super.build(); + } + } + + private Tonlib tonlib; + private long wc; + + @Override + public Tonlib getTonlib() { + return tonlib; + } + + @Override + public long getWorkchain() { + return wc; + } + + @Override + public String getName() { + return "multisig"; + } + + /** + * Initial contract storage (init state). Creator/deployer will be always part of k signatures. By + * default, it will reside in owner_infos dict at index 0. + * + * @return Cell + */ + @Override + public Cell createDataCell() { + CellBuilder cell = CellBuilder.beginCell(); + + cell.storeUint(walletId, 32); + cell.storeUint(config.getN(), 8); // n + cell.storeUint(config.getK(), 8); // k - collect at least k signatures + cell.storeUint(0, 64); // last cleaned + if (isNull(config.getOwners()) || config.getOwners().isEmpty()) { + cell.storeBit(false); // initial owners dict + } else { + cell.storeDict(createOwnersInfoDict(config.getOwners())); + } + + if (isNull(config.getPendingQueries()) || config.getPendingQueries().isEmpty()) { + cell.storeBit(false); // initial pending queries dict + } else { + cell.storeDict(createPendingQueries(config.getPendingQueries(), config.getN())); + } + + return cell.endCell(); + } + + @Override + public Cell createCodeCell() { + return CellBuilder.beginCell().fromBoc(WalletCodes.multisig.getValue()).endCell(); + } + + private Cell createSigningMessageInternal(int pubkeyIndex, Cell order) { + return CellBuilder.beginCell() + .storeUint(pubkeyIndex, 8) // root-id - pk-index for owner_infos dict + .storeCell(order) + .endCell(); + } + + public List getPublicKeys() { + + List publicKeys = new ArrayList<>(); + + Address myAddress = this.getAddress(); + RunResult result = tonlib.runMethod(myAddress, "get_public_keys"); + + if (result.getExit_code() != 0) { + throw new Error("method get_public_keys, returned an exit code " + result.getExit_code()); + } + + TvmStackEntryCell cellResult = (TvmStackEntryCell) result.getStack().get(0); + Cell cell = CellBuilder.beginCell().fromBocBase64(cellResult.getCell().getBytes()).endCell(); + // Cell cell = Cell.fromBocBase64(cellResult.getCell().getBytes()); + + CellSlice cs = CellSlice.beginParse(cell); + TonHashMap loadedDict = + cs.loadDict( + 8, + k -> k.readUint(8), // index + v -> v // ownerInfo cell + ); + for (Map.Entry entry : loadedDict.elements.entrySet()) { + CellSlice cSlice = CellSlice.beginParse((Cell) entry.getValue()); + BigInteger pubKey = cSlice.loadUint(256); + publicKeys.add(pubKey); + } + return publicKeys; + } + + public List getPublicKeysHex() { + List l = getPublicKeys(); + List result = new ArrayList<>(); + for (BigInteger i : l) { + result.add(i.toString(16)); + } + return result; + } + + /** + * generates and returns init-state onchain + * + * @param walletId walletid + * @param n total keys + * @param k minimum number of keys + * @param ownersInfo arrays with public keys + * @return cell with state-init + */ + public Cell getInitState(long walletId, int n, int k, Cell ownersInfo) { + + Address myAddress = this.getAddress(); + Deque stack = new ArrayDeque<>(); + + stack.offer("[num, " + walletId + "]"); + stack.offer("[num, " + n + "]"); + stack.offer("[num, " + k + "]"); + stack.offer("[cell, " + ownersInfo.toHex(false) + "]"); + RunResult result = tonlib.runMethod(myAddress, "create_init_state", stack); + + if (result.getExit_code() != 0) { + throw new Error("method createInitState, returned an exit code " + result.getExit_code()); + } + + TvmStackEntryCell domainCell = (TvmStackEntryCell) result.getStack().get(0); + return CellBuilder.beginCell().fromBocBase64(domainCell.getCell().getBytes()).endCell(); + } + + /** + * Sends an external msg with the order containing all collected signatures signed by owner at + * index pubkeyIndex with keyPair. + * + * @param keyPair TweetNaclFast.Signature.KeyPair + */ + public ExtMessageInfo sendOrder( + TweetNaclFast.Signature.KeyPair keyPair, int pubkeyIndex, Cell order) { + + Cell signingMessageBody = createSigningMessageInternal(pubkeyIndex, order); + + Message externalMessage = + Message.builder() + .info(ExternalMessageInInfo.builder().dstAddr(getAddressIntStd()).build()) + .body( + CellBuilder.beginCell() + .storeBytes( + Utils.signData( + keyPair.getPublicKey(), + keyPair.getSecretKey(), + signingMessageBody.hash())) + .storeCell(signingMessageBody) + .endCell()) + .build(); + return tonlib.sendRawMessage(externalMessage.toCell().toBase64()); + } + + /** + * Sends an external msg with the order containing all collected signatures signed by owner at + * index pubkeyIndex with secretKey. + * + * @param secretKey byte[] + */ + public ExtMessageInfo sendOrder(byte[] secretKey, int pubkeyIndex, Cell order) { + Cell signingMessageBody = createSigningMessageInternal(pubkeyIndex, order); + + Message externalMessage = + Message.builder() + .info(ExternalMessageInInfo.builder().dstAddr(getAddressIntStd()).build()) + .body( + CellBuilder.beginCell() + .storeBytes( + Utils.signData( + keyPair.getPublicKey(), secretKey, signingMessageBody.hash())) + .storeCell(signingMessageBody) + .endCell()) + .build(); + return tonlib.sendRawMessage(externalMessage.toCell().toBase64()); + } + + /** + * Serializes list of multisig wallet owners. + * + * @param ownersInfo OwnerInfo + * @return Cell + */ + public Cell createOwnersInfoDict(List ownersInfo) { + int dictKeySize = 8; + TonHashMapE dictDestinations = new TonHashMapE(dictKeySize); + + long i = 0; // key, index 16bit + for (OwnerInfo ownerInfo : ownersInfo) { + + CellBuilder ownerInfoCell = CellBuilder.beginCell(); + ownerInfoCell.storeBytes(ownerInfo.getPublicKey()); // 256 bits + ownerInfoCell.storeUint(ownerInfo.getFlood(), 8); + + dictDestinations.elements.put( + i++, // key - index + ownerInfoCell.endCell() // value - cell - OwnerInfo + ); + } + + Cell cellDict = + dictDestinations.serialize( + k -> CellBuilder.beginCell().storeUint((Long) k, dictKeySize).endCell().getBits(), + v -> (Cell) v); + + return cellDict; + } + + public static Cell createPendingQueries(List pendingQueries, int n) { + int dictKeySize = 64; + TonHashMapE dictDestinations = new TonHashMapE(dictKeySize); + + long i = 0; // key, index 16bit + for (PendingQuery query : pendingQueries) { + + CellBuilder queryCell = CellBuilder.beginCell(); + queryCell.storeBit(true); + queryCell.storeUint(query.getCreatorI(), 8); + queryCell.storeUint(query.getCnt(), 8); + queryCell.storeUint(query.getCntBits(), n); + queryCell.storeCell(query.getMsg()); + + dictDestinations.elements.put( + query.getQueryId(), // key - query-id + queryCell.endCell() // value - cell - QueryData + ); + } + + Cell cellDict = + dictDestinations.serialize( + k -> CellBuilder.beginCell().storeUint((BigInteger) k, dictKeySize).endCell().getBits(), + v -> (Cell) v); + + return cellDict; + } + + public static Cell createSignaturesDict(List signatures) { + int dictKeySize = 8; // what is the size of the key? + TonHashMapE dictSignatures = new TonHashMapE(dictKeySize); + + long i = 0; // key, index + for (byte[] signature : signatures) { + + CellBuilder sigCell = CellBuilder.beginCell(); + sigCell.storeBytes(signature); + sigCell.storeUint(i, 8); + + dictSignatures.elements.put( + i, // key - index + sigCell.endCell() // value - cell - Signature, 512+8 + ); + i++; + } + + Cell cellDict = + dictSignatures.serialize( + k -> CellBuilder.beginCell().storeUint((Long) k, dictKeySize).endCell().getBits(), + v -> (Cell) v); + + return cellDict; + } + + /** + * Serialized list of signatures into cell + * + * @param i start index + * @param signatures list of signatures + * @return Cell + */ + public static Cell serializeSignatures(int i, List signatures) { + + CellBuilder c = CellBuilder.beginCell(); + c.storeBytes(signatures.get(i).getSignature()); + c.storeUint(signatures.get(i).getPubKeyPosition(), 8); + if (i == signatures.size() - 1) { + c.storeBit(false); // empty dict, last cell + } else { + c.storeBit(true); + c.storeRef(serializeSignatures(++i, signatures)); + } + return c.endCell(); + } + + public static Cell createQuery( + TweetNaclFast.Signature.KeyPair keyPair, List signatures, Cell order) { + + CellBuilder rootCell = CellBuilder.beginCell(); + rootCell.storeUint(0, 8); // root-i + if (isNull(signatures) || signatures.isEmpty()) { + rootCell.storeBit(false); + } else { + rootCell.storeBit(true); + rootCell.storeRef(serializeSignatures(0, signatures)); + } + CellSlice cs = CellSlice.beginParse(order); + cs.skipBit(); // remove no-signatures flag + CellBuilder o = CellBuilder.beginCell(); + o.storeCell(cs.sliceToCell()); + + rootCell.storeCell(o.endCell()); + + byte[] rootSignature = signCell(keyPair, rootCell.endCell()); + + CellBuilder query = CellBuilder.beginCell(); + query.storeBytes(rootSignature); + query.storeCell(rootCell.endCell()); + + return query.endCell(); + } + + public ExtMessageInfo deploy() { + + Message externalMessage = + Message.builder() + .info(ExternalMessageInInfo.builder().dstAddr(getAddressIntStd()).build()) + .init(getStateInit()) + .build(); + + return tonlib.sendRawMessage(externalMessage.toCell().toBase64()); + } + + /** + * @param destination address + * @param amount values in nano-tons + * @param mode send mode + * @return Cell + */ + public static Cell createOneInternalMsg(Address destination, BigInteger amount, int mode) { + Message internalMessage = + Message.builder() + .info( + InternalMessageInfo.builder() + .dstAddr( + MsgAddressIntStd.builder() + .workchainId(destination.wc) + .address(destination.toBigInteger()) + .build()) + .value(CurrencyCollection.builder().coins(amount).build()) + .build()) + .build(); + + CellBuilder p = CellBuilder.beginCell(); + p.storeUint(mode, 8); + p.storeRef(internalMessage.toCell()); + + return p.endCell(); + } + + /** + * @param internalMsgs List of Cells, where Cell is internal msg, defining target destinations + * with amounts + * @return Cell Order + */ + public static Cell createOrder(Long walletId, BigInteger queryId, Cell... internalMsgs) { + if (internalMsgs.length > 3) { + throw new Error("Order cannot contain more than 3 internal messages"); + } + CellBuilder order = CellBuilder.beginCell(); + order.storeBit(false); // no signatures + order.storeUint(walletId, 32); + order.storeUint(queryId, 64); + + for (Cell msg : internalMsgs) { + order.storeCell(msg); + } + return order.endCell(); + } + + public static Cell createOrder1(Long walletId, BigInteger queryId, Cell... internalMsgs) { + if (internalMsgs.length > 3) { + throw new Error("Order cannot contain more than 3 internal messages"); + } + CellBuilder order = CellBuilder.beginCell(); + order.storeUint(walletId, 32); + order.storeUint(queryId, 64); + + for (Cell msg : internalMsgs) { + order.storeCell(msg); + } + return order.endCell(); + } + + public static Cell addSignatures(Cell order, List signatures) { + + CellBuilder signedOrder = CellBuilder.beginCell(); + signedOrder.storeBit(true); // contains signatures + signedOrder.storeRef(serializeSignatures(0, signatures)); + + CellSlice cs = CellSlice.beginParse(order); + cs.skipBit(); // remove no-signatures flag + CellBuilder o = CellBuilder.beginCell(); + o.storeCell(cs.sliceToCell()); + + signedOrder.storeCell(o.endCell()); + return signedOrder.endCell(); + } + + private static void checkIfSignatureExists(Cell order, byte[] signature) { + CellSlice cs = CellSlice.beginParse(order); + + if (cs.loadBit()) { // order contains signatures + Cell ref = cs.loadRef(); + while (nonNull(ref)) { + byte[] sig = CellSlice.beginParse(ref).loadBytes(512); + log.info("sig " + Utils.bytesToHex(signature)); + if (sig == signature) { + throw new Error("Your signature is already presented"); } - - if (isNull(config.getPendingQueries()) || config.getPendingQueries().isEmpty()) { - cell.storeBit(false); // initial pending queries dict + if (ref.getUsedRefs() != 0) { + ref = ref.getRefs().get(0); } else { - cell.storeDict(createPendingQueries(config.getPendingQueries(), config.getN())); + ref = null; } - - return cell.endCell(); + } } + } - @Override - public Cell createCodeCell() { - return CellBuilder.beginCell(). - fromBoc(WalletCodes.multisig.getValue()). - endCell(); - } + public static Cell addSignature1( + Cell order, int pubkeyIndex, TweetNaclFast.Signature.KeyPair keyPair) { - private Cell createSigningMessageInternal(int pubkeyIndex, Cell order) { - return CellBuilder.beginCell() - .storeUint(pubkeyIndex, 8) // root-id - pk-index for owner_infos dict - .storeCell(order) - .endCell(); - } + CellSlice cs = CellSlice.beginParse(order); + cs.skipBit(); // remove no-signatures flag + CellBuilder o = CellBuilder.beginCell(); + o.storeCell(cs.sliceToCell()); + byte[] signature = signCell(keyPair, o.endCell()); - public List getPublicKeys() { + log.info("sig " + Utils.bytesToHex(signature)); - List publicKeys = new ArrayList<>(); + // checkIfSignatureExists(order, signature); - Address myAddress = this.getAddress(); - RunResult result = tonlib.runMethod(myAddress, "get_public_keys"); + cs = CellSlice.beginParse(order); + if (!cs.loadBit()) { // order didn't have any signatures, add first signature + cs.skipBit(); // remove no-signatures flag - if (result.getExit_code() != 0) { - throw new Error("method get_public_keys, returned an exit code " + result.getExit_code()); - } + CellBuilder signedOrder = CellBuilder.beginCell(); + signedOrder.storeBit(true); // contains signatures - TvmStackEntryCell cellResult = (TvmStackEntryCell) result.getStack().get(0); - Cell cell = CellBuilder.beginCell().fromBocBase64(cellResult.getCell().getBytes()).endCell(); -// Cell cell = Cell.fromBocBase64(cellResult.getCell().getBytes()); - - CellSlice cs = CellSlice.beginParse(cell); - TonHashMap loadedDict = cs.loadDict(8, - k -> k.readUint(8), // index - v -> v // ownerInfo cell - ); - for (Map.Entry entry : loadedDict.elements.entrySet()) { - CellSlice cSlice = CellSlice.beginParse((Cell) entry.getValue()); - BigInteger pubKey = cSlice.loadUint(256); - publicKeys.add(pubKey); - } - return publicKeys; - } + CellBuilder c = CellBuilder.beginCell(); + c.storeBytes(signature); + c.storeUint(pubkeyIndex, 8); + c.storeBit(false); // no more references, only one signature added - public List getPublicKeysHex() { - List l = getPublicKeys(); - List result = new ArrayList<>(); - for (BigInteger i : l) { - result.add(i.toString(16)); - } - return result; - } - - /** - * generates and returns init-state onchain - * - * @param walletId walletid - * @param n total keys - * @param k minimum number of keys - * @param ownersInfo arrays with public keys - * @return cell with state-init - */ - public Cell getInitState(long walletId, int n, int k, Cell ownersInfo) { - - Address myAddress = this.getAddress(); - Deque stack = new ArrayDeque<>(); - - stack.offer("[num, " + walletId + "]"); - stack.offer("[num, " + n + "]"); - stack.offer("[num, " + k + "]"); - stack.offer("[cell, " + ownersInfo.toHex(false) + "]"); - RunResult result = tonlib.runMethod(myAddress, "create_init_state", stack); - - if (result.getExit_code() != 0) { - throw new Error("method createInitState, returned an exit code " + result.getExit_code()); - } + signedOrder.storeRef(c.endCell()); + signedOrder.storeCell(o.endCell()); + return signedOrder.endCell(); + } else { // order contains some signatures + Cell otherSignatures = cs.loadRef(); - TvmStackEntryCell domainCell = (TvmStackEntryCell) result.getStack().get(0); - return CellBuilder.beginCell().fromBocBase64(domainCell.getCell().getBytes()).endCell(); - } - - /** - * Sends an external msg with the order containing all collected signatures signed by owner at index - * pubkeyIndex with keyPair. - * - * @param keyPair TweetNaclFast.Signature.KeyPair - */ - public ExtMessageInfo sendOrder(TweetNaclFast.Signature.KeyPair keyPair, int pubkeyIndex, Cell order) { - - Cell signingMessageBody = createSigningMessageInternal(pubkeyIndex, order); - - Message externalMessage = Message.builder() - .info(ExternalMessageInInfo.builder() - .dstAddr(getAddressIntStd()) - .build()) - .body(CellBuilder.beginCell() - .storeBytes(Utils.signData(keyPair.getPublicKey(), keyPair.getSecretKey(), signingMessageBody.hash())) - .storeCell(signingMessageBody) - .endCell()) - .build(); - return tonlib.sendRawMessage(externalMessage.toCell().toBase64()); - } - - /** - * Sends an external msg with the order containing all collected signatures signed by owner at index - * pubkeyIndex with secretKey. - * - * @param secretKey byte[] - */ - public ExtMessageInfo sendOrder(byte[] secretKey, int pubkeyIndex, Cell order) { - Cell signingMessageBody = createSigningMessageInternal(pubkeyIndex, order); - - Message externalMessage = Message.builder() - .info(ExternalMessageInInfo.builder() - .dstAddr(getAddressIntStd()) - .build()) - .body(CellBuilder.beginCell() - .storeBytes(Utils.signData(keyPair.getPublicKey(), secretKey, signingMessageBody.hash())) - .storeCell(signingMessageBody) - .endCell()) - .build(); - return tonlib.sendRawMessage(externalMessage.toCell().toBase64()); - } - - /** - * Serializes list of multisig wallet owners. - * - * @param ownersInfo OwnerInfo - * @return Cell - */ - public Cell createOwnersInfoDict(List ownersInfo) { - int dictKeySize = 8; - TonHashMapE dictDestinations = new TonHashMapE(dictKeySize); - - long i = 0; // key, index 16bit - for (OwnerInfo ownerInfo : ownersInfo) { - - CellBuilder ownerInfoCell = CellBuilder.beginCell(); - ownerInfoCell.storeBytes(ownerInfo.getPublicKey()); //256 bits - ownerInfoCell.storeUint(ownerInfo.getFlood(), 8); - - dictDestinations.elements.put( - i++, // key - index - ownerInfoCell.endCell() // value - cell - OwnerInfo - ); - } + CellBuilder signedOrder = CellBuilder.beginCell(); + signedOrder.storeBit(true); // contains signatures - Cell cellDict = dictDestinations.serialize( - k -> CellBuilder.beginCell().storeUint((Long) k, dictKeySize).endCell().getBits(), - v -> (Cell) v - ); + CellBuilder c = CellBuilder.beginCell(); + c.storeBytes(signature); // add new signature + c.storeUint(pubkeyIndex, 8); + c.storeBit(true); // add other signatures + c.storeRef(otherSignatures); - return cellDict; + signedOrder.storeRef(c.endCell()); + signedOrder.storeCell(o.endCell()); + return signedOrder.endCell(); } + } - public static Cell createPendingQueries(List pendingQueries, int n) { - int dictKeySize = 64; - TonHashMapE dictDestinations = new TonHashMapE(dictKeySize); + public static byte[] signCell(TweetNaclFast.Signature.KeyPair keyPair, Cell cell) { + return new TweetNaclFast.Signature(keyPair.getPublicKey(), keyPair.getSecretKey()) + .detached(cell.hash()); + } - long i = 0; // key, index 16bit - for (PendingQuery query : pendingQueries) { + public static byte[] signOrder(TweetNaclFast.Signature.KeyPair keyPair, Cell order) { + CellSlice cs = CellSlice.beginParse(order); + cs.skipBit(); // remove no-signatures flag + CellBuilder o = CellBuilder.beginCell(); + o.storeCell(cs.sliceToCell()); - CellBuilder queryCell = CellBuilder.beginCell(); - queryCell.storeBit(true); - queryCell.storeUint(query.getCreatorI(), 8); - queryCell.storeUint(query.getCnt(), 8); - queryCell.storeUint(query.getCntBits(), n); - queryCell.storeCell(query.getMsg()); + return signCell(keyPair, o.endCell()); + } - dictDestinations.elements.put( - query.getQueryId(), // key - query-id - queryCell.endCell() // value - cell - QueryData - ); - } + public Pair getNandK() { - Cell cellDict = dictDestinations.serialize( - k -> CellBuilder.beginCell().storeUint((BigInteger) k, dictKeySize).endCell().getBits(), - v -> (Cell) v - ); + Address myAddress = getAddress(); + RunResult result = tonlib.runMethod(myAddress, "get_n_k"); - return cellDict; + if (result.getExit_code() != 0) { + throw new Error("method get_n_k, returned an exit code " + result.getExit_code()); } - public static Cell createSignaturesDict(List signatures) { - int dictKeySize = 8; // what is the size of the key? - TonHashMapE dictSignatures = new TonHashMapE(dictKeySize); + TvmStackEntryNumber nNumber = (TvmStackEntryNumber) result.getStack().get(0); + TvmStackEntryNumber kNumber = (TvmStackEntryNumber) result.getStack().get(1); - long i = 0; // key, index - for (byte[] signature : signatures) { + return Pair.of(nNumber.getNumber().longValue(), kNumber.getNumber().longValue()); + } - CellBuilder sigCell = CellBuilder.beginCell(); - sigCell.storeBytes(signature); - sigCell.storeUint(i, 8); + /** + * Returns list of all unsigned messages + * + * @return List pending queries + */ + public Map getMessagesUnsigned() { - dictSignatures.elements.put( - i, // key - index - sigCell.endCell() // value - cell - Signature, 512+8 - ); - i++; - } + Address myAddress = this.getAddress(); + RunResult result = tonlib.runMethod(myAddress, "get_messages_unsigned"); - Cell cellDict = dictSignatures.serialize( - k -> CellBuilder.beginCell().storeUint((Long) k, dictKeySize).endCell().getBits(), - v -> (Cell) v - ); - - return cellDict; + if (result.getExit_code() != 0) { + throw new Error( + "method get_messages_unsigned, returned an exit code " + result.getExit_code()); } - /** - * Serialized list of signatures into cell - * - * @param i start index - * @param signatures list of signatures - * @return Cell - */ - public static Cell serializeSignatures(int i, List signatures) { - - CellBuilder c = CellBuilder.beginCell(); - c.storeBytes(signatures.get(i).getSignature()); - c.storeUint(signatures.get(i).getPubKeyPosition(), 8); - if (i == signatures.size() - 1) { - c.storeBit(false); // empty dict, last cell - } else { - c.storeBit(true); - c.storeRef(serializeSignatures(++i, signatures)); - } - return c.endCell(); + if (result.getStack().get(0) instanceof TvmStackEntryList) { + return new HashMap<>(); } - public static Cell createQuery(TweetNaclFast.Signature.KeyPair keyPair, List signatures, Cell order) { + TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); + Cell cellDict = CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); - CellBuilder rootCell = CellBuilder.beginCell(); - rootCell.storeUint(0, 8); // root-i - if (isNull(signatures) || signatures.isEmpty()) { - rootCell.storeBit(false); - } else { - rootCell.storeBit(true); - rootCell.storeRef(serializeSignatures(0, signatures)); - } - CellSlice cs = CellSlice.beginParse(order); - cs.skipBit(); // remove no-signatures flag - CellBuilder o = CellBuilder.beginCell(); - o.storeCell(cs.sliceToCell()); + CellSlice cs = CellSlice.beginParse(cellDict); - rootCell.storeCell(o.endCell()); + TonHashMap loadedDict = cs.loadDict(64, k -> k.readUint(64), v -> v); - byte[] rootSignature = signCell(keyPair, rootCell.endCell()); - - CellBuilder query = CellBuilder.beginCell(); - query.storeBytes(rootSignature); - query.storeCell(rootCell.endCell()); - - return query.endCell(); + Map resultMap = new HashMap<>(); + for (Map.Entry entry : loadedDict.elements.entrySet()) { + // query-id, query + resultMap.put((BigInteger) entry.getKey(), (Cell) entry.getValue()); } - public ExtMessageInfo deploy() { - - Message externalMessage = Message.builder() - .info(ExternalMessageInInfo.builder() - .dstAddr(getAddressIntStd()) - .build()) - .init(getStateInit()) - .build(); + return resultMap; + } - return tonlib.sendRawMessage(externalMessage.toCell().toBase64()); - } - - /** - * @param destination address - * @param amount values in nano-tons - * @param mode send mode - * @return Cell - */ - public static Cell createOneInternalMsg(Address destination, BigInteger amount, int mode) { - Message internalMessage = Message.builder() - .info(InternalMessageInfo.builder() - .dstAddr(MsgAddressIntStd.builder() - .workchainId(destination.wc) - .address(destination.toBigInteger()) - .build()) - .value(CurrencyCollection.builder().coins(amount).build()) - .build()).build(); + /** + * Returns list of all signed messages by index + * + * @return List pending queries + */ + public Map getMessagesSignedByIndex(long index) { - CellBuilder p = CellBuilder.beginCell(); - p.storeUint(mode, 8); - p.storeRef(internalMessage.toCell()); + Address myAddress = this.getAddress(); + Deque stack = new ArrayDeque<>(); - return p.endCell(); - } + stack.offer("[num, " + index + "]"); - /** - * @param internalMsgs List of Cells, where Cell is internal msg, defining target destinations with amounts - * @return Cell Order - */ - public static Cell createOrder(Long walletId, BigInteger queryId, Cell... internalMsgs) { - if (internalMsgs.length > 3) { - throw new Error("Order cannot contain more than 3 internal messages"); - } - CellBuilder order = CellBuilder.beginCell(); - order.storeBit(false); // no signatures - order.storeUint(walletId, 32); - order.storeUint(queryId, 64); + RunResult result = tonlib.runMethod(myAddress, "get_messages_signed_by_id", stack); - for (Cell msg : internalMsgs) { - order.storeCell(msg); - } - return order.endCell(); + if (result.getExit_code() != 0) { + throw new Error( + "method get_messages_signed_by_id, returned an exit code " + result.getExit_code()); } - public static Cell createOrder1(Long walletId, BigInteger queryId, Cell... internalMsgs) { - if (internalMsgs.length > 3) { - throw new Error("Order cannot contain more than 3 internal messages"); - } - CellBuilder order = CellBuilder.beginCell(); - order.storeUint(walletId, 32); - order.storeUint(queryId, 64); - - for (Cell msg : internalMsgs) { - order.storeCell(msg); - } - return order.endCell(); + if (result.getStack().get(0) instanceof TvmStackEntryList) { + return new HashMap<>(); } - public static Cell addSignatures(Cell order, List signatures) { + TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); + Cell cellDict = CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); - CellBuilder signedOrder = CellBuilder.beginCell(); - signedOrder.storeBit(true); // contains signatures - signedOrder.storeRef(serializeSignatures(0, signatures)); + CellSlice cs = CellSlice.beginParse(cellDict); - CellSlice cs = CellSlice.beginParse(order); - cs.skipBit(); // remove no-signatures flag - CellBuilder o = CellBuilder.beginCell(); - o.storeCell(cs.sliceToCell()); + TonHashMap loadedDict = cs.loadDict(64, k -> k.readUint(64), v -> v); - signedOrder.storeCell(o.endCell()); - return signedOrder.endCell(); + Map resultMap = new HashMap<>(); + for (Map.Entry entry : loadedDict.elements.entrySet()) { + // query-id, query + resultMap.put((BigInteger) entry.getKey(), (Cell) entry.getValue()); } + return resultMap; + } - private static void checkIfSignatureExists(Cell order, byte[] signature) { - CellSlice cs = CellSlice.beginParse(order); + /** + * Returns list of all unsigned messages by index + * + * @return List pending queries + */ + public Map getMessagesUnsignedByIndex(long index) { - if (cs.loadBit()) { //order contains signatures - Cell ref = cs.loadRef(); - while (nonNull(ref)) { - byte[] sig = CellSlice.beginParse(ref).loadBytes(512); - log.info("sig " + Utils.bytesToHex(signature)); - if (sig == signature) { - throw new Error("Your signature is already presented"); - } - if (ref.getUsedRefs() != 0) { - ref = ref.getRefs().get(0); - } else { - ref = null; - } - } - } - } - - public static Cell addSignature1(Cell order, int pubkeyIndex, TweetNaclFast.Signature.KeyPair keyPair) { - - CellSlice cs = CellSlice.beginParse(order); - cs.skipBit(); // remove no-signatures flag - CellBuilder o = CellBuilder.beginCell(); - o.storeCell(cs.sliceToCell()); - - byte[] signature = signCell(keyPair, o.endCell()); - - log.info("sig " + Utils.bytesToHex(signature)); + Address myAddress = this.getAddress(); + Deque stack = new ArrayDeque<>(); -// checkIfSignatureExists(order, signature); + stack.offer("[num, " + index + "]"); - cs = CellSlice.beginParse(order); - if (!cs.loadBit()) { //order didn't have any signatures, add first signature - cs.skipBit(); // remove no-signatures flag + RunResult result = tonlib.runMethod(myAddress, "get_messages_unsigned_by_id", stack); - CellBuilder signedOrder = CellBuilder.beginCell(); - signedOrder.storeBit(true); // contains signatures - - CellBuilder c = CellBuilder.beginCell(); - c.storeBytes(signature); - c.storeUint(pubkeyIndex, 8); - c.storeBit(false); // no more references, only one signature added - - signedOrder.storeRef(c.endCell()); - signedOrder.storeCell(o.endCell()); - return signedOrder.endCell(); - } else { // order contains some signatures - Cell otherSignatures = cs.loadRef(); - - CellBuilder signedOrder = CellBuilder.beginCell(); - signedOrder.storeBit(true); // contains signatures - - CellBuilder c = CellBuilder.beginCell(); - c.storeBytes(signature); // add new signature - c.storeUint(pubkeyIndex, 8); - c.storeBit(true); // add other signatures - c.storeRef(otherSignatures); - - signedOrder.storeRef(c.endCell()); - signedOrder.storeCell(o.endCell()); - return signedOrder.endCell(); - } + if (result.getExit_code() != 0) { + throw new Error( + "method get_messages_unsigned_by_id, returned an exit code " + result.getExit_code()); } - - public static byte[] signCell(TweetNaclFast.Signature.KeyPair keyPair, Cell cell) { - return new TweetNaclFast.Signature(keyPair.getPublicKey(), keyPair.getSecretKey()).detached(cell.hash()); + if (result.getStack().get(0) instanceof TvmStackEntryList) { + return new HashMap<>(); } - public static byte[] signOrder(TweetNaclFast.Signature.KeyPair keyPair, Cell order) { - CellSlice cs = CellSlice.beginParse(order); - cs.skipBit(); // remove no-signatures flag - CellBuilder o = CellBuilder.beginCell(); - o.storeCell(cs.sliceToCell()); + TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); + Cell cellDict = CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); - return signCell(keyPair, o.endCell()); - } + CellSlice cs = CellSlice.beginParse(cellDict); - public Pair getNandK() { + TonHashMap loadedDict = cs.loadDict(64, k -> k.readUint(64), v -> v); - Address myAddress = getAddress(); - RunResult result = tonlib.runMethod(myAddress, "get_n_k"); - - if (result.getExit_code() != 0) { - throw new Error("method get_n_k, returned an exit code " + result.getExit_code()); - } - - TvmStackEntryNumber nNumber = (TvmStackEntryNumber) result.getStack().get(0); - TvmStackEntryNumber kNumber = (TvmStackEntryNumber) result.getStack().get(1); - - return Pair.of(nNumber.getNumber().longValue(), kNumber.getNumber().longValue()); + Map resultMap = new HashMap<>(); + for (Map.Entry entry : loadedDict.elements.entrySet()) { + // query-id, query + resultMap.put((BigInteger) entry.getKey(), (Cell) entry.getValue()); } + return resultMap; + } - /** - * Returns list of all unsigned messages - * - * @return List pending queries - */ - public Map getMessagesUnsigned() { - - Address myAddress = this.getAddress(); - RunResult result = tonlib.runMethod(myAddress, "get_messages_unsigned"); - - if (result.getExit_code() != 0) { - throw new Error("method get_messages_unsigned, returned an exit code " + result.getExit_code()); - } - - if (result.getStack().get(0) instanceof TvmStackEntryList) { - return new HashMap<>(); - } + /** + * Returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) and the mask of + * signed positions of pubkeys + * + * @return Pair status, mask + */ + public Pair getQueryState(BigInteger queryId) { - TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); - Cell cellDict = CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); + Address myAddress = this.getAddress(); - CellSlice cs = CellSlice.beginParse(cellDict); + Deque stack = new ArrayDeque<>(); - TonHashMap loadedDict = cs - .loadDict(64, - k -> k.readUint(64), - v -> v - ); + stack.offer("[num, " + queryId.toString(10) + "]"); - Map resultMap = new HashMap<>(); - for (Map.Entry entry : loadedDict.elements.entrySet()) { - // query-id, query - resultMap.put((BigInteger) entry.getKey(), (Cell) entry.getValue()); - } + RunResult result = tonlib.runMethod(myAddress, "get_query_state", stack); - return resultMap; + if (result.getExit_code() != 0) { + throw new Error("method get_query_state, returned an exit code " + result.getExit_code()); } + TvmStackEntryNumber r = (TvmStackEntryNumber) result.getStack().get(0); + TvmStackEntryNumber n = (TvmStackEntryNumber) result.getStack().get(1); + return Pair.of(r.getNumber().longValue(), n.getNumber().longValue()); + } - /** - * Returns list of all signed messages by index - * - * @return List pending queries - */ - public Map getMessagesSignedByIndex(long index) { - - Address myAddress = this.getAddress(); - Deque stack = new ArrayDeque<>(); + /** + * You can check whether signatures used to sign the order are correct + * + * @param tonlib Tonlib + * @param query Cell of serialized list of signatures and order + * @return Pair count of correct signatures and the mask + */ + public Pair checkQuerySignatures(Tonlib tonlib, Cell query) { - stack.offer("[num, " + index + "]"); + Address myAddress = this.getAddress(); + Deque stack = new ArrayDeque<>(); - RunResult result = tonlib.runMethod(myAddress, "get_messages_signed_by_id", stack); - - if (result.getExit_code() != 0) { - throw new Error("method get_messages_signed_by_id, returned an exit code " + result.getExit_code()); - } - - if (result.getStack().get(0) instanceof TvmStackEntryList) { - return new HashMap<>(); - } + stack.offer("[cell, " + query.toHex(false) + "]"); + RunResult result = tonlib.runMethod(myAddress, "check_query_signatures", stack); - TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); - Cell cellDict = CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); - - CellSlice cs = CellSlice.beginParse(cellDict); - - TonHashMap loadedDict = cs - .loadDict(64, - k -> k.readUint(64), - v -> v - ); - - Map resultMap = new HashMap<>(); - for (Map.Entry entry : loadedDict.elements.entrySet()) { - // query-id, query - resultMap.put((BigInteger) entry.getKey(), (Cell) entry.getValue()); - } - return resultMap; + if (result.getExit_code() != 0) { + throw new Error( + "method check_query_signatures, returned an exit code " + result.getExit_code()); } + TvmStackEntryNumber cnt = (TvmStackEntryNumber) result.getStack().get(0); + TvmStackEntryNumber mask = (TvmStackEntryNumber) result.getStack().get(1); - /** - * Returns list of all unsigned messages by index - * - * @return List pending queries - */ - public Map getMessagesUnsignedByIndex(long index) { + return Pair.of(cnt.getNumber().longValue(), mask.getNumber().longValue()); + } - Address myAddress = this.getAddress(); - Deque stack = new ArrayDeque<>(); + public Cell mergePendingQueries(Tonlib tonlib, Cell a, Cell b) { - stack.offer("[num, " + index + "]"); + Address myAddress = this.getAddress(); + Deque stack = new ArrayDeque<>(); - RunResult result = tonlib.runMethod(myAddress, "get_messages_unsigned_by_id", stack); + stack.offer("[cell, " + a.toHex(false) + "]"); + stack.offer("[cell, " + b.toHex(false) + "]"); + RunResult result = tonlib.runMethod(myAddress, "merge_inner_queries", stack); - if (result.getExit_code() != 0) { - throw new Error("method get_messages_unsigned_by_id, returned an exit code " + result.getExit_code()); - } - - if (result.getStack().get(0) instanceof TvmStackEntryList) { - return new HashMap<>(); - } - - TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); - Cell cellDict = CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); - - CellSlice cs = CellSlice.beginParse(cellDict); - - TonHashMap loadedDict = cs - .loadDict(64, - k -> k.readUint(64), - v -> v - ); - - Map resultMap = new HashMap<>(); - for (Map.Entry entry : loadedDict.elements.entrySet()) { - // query-id, query - resultMap.put((BigInteger) entry.getKey(), (Cell) entry.getValue()); - } - return resultMap; + if (result.getExit_code() != 0) { + throw new Error("method merge_inner_queries, returned an exit code " + result.getExit_code()); } - /** - * Returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) - * and the mask of signed positions of pubkeys - * - * @return Pair status, mask - */ - public Pair getQueryState(BigInteger queryId) { - - Address myAddress = this.getAddress(); + TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); - Deque stack = new ArrayDeque<>(); + return CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); + } - stack.offer("[num, " + queryId.toString(10) + "]"); + /** + * Returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) + * + * @return Long status + */ + public long processed(Tonlib tonlib, BigInteger queryId) { - RunResult result = tonlib.runMethod(myAddress, "get_query_state", stack); + Address myAddress = this.getAddress(); - if (result.getExit_code() != 0) { - throw new Error("method get_query_state, returned an exit code " + result.getExit_code()); - } + Deque stack = new ArrayDeque<>(); - TvmStackEntryNumber r = (TvmStackEntryNumber) result.getStack().get(0); - TvmStackEntryNumber n = (TvmStackEntryNumber) result.getStack().get(1); - return Pair.of(r.getNumber().longValue(), n.getNumber().longValue()); - } + stack.offer("[num, " + queryId.toString(10) + "]"); - /** - * You can check whether signatures used to sign the order are correct - * - * @param tonlib Tonlib - * @param query Cell of serialized list of signatures and order - * @return Pair count of correct signatures and the mask - */ - public Pair checkQuerySignatures(Tonlib tonlib, Cell query) { - - Address myAddress = this.getAddress(); - Deque stack = new ArrayDeque<>(); - - stack.offer("[cell, " + query.toHex(false) + "]"); - RunResult result = tonlib.runMethod(myAddress, "check_query_signatures", stack); - - if (result.getExit_code() != 0) { - throw new Error("method check_query_signatures, returned an exit code " + result.getExit_code()); - } - - TvmStackEntryNumber cnt = (TvmStackEntryNumber) result.getStack().get(0); - TvmStackEntryNumber mask = (TvmStackEntryNumber) result.getStack().get(1); - - return Pair.of(cnt.getNumber().longValue(), mask.getNumber().longValue()); - } + RunResult result = tonlib.runMethod(myAddress, "processed?", stack); - public Cell mergePendingQueries(Tonlib tonlib, Cell a, Cell b) { - - Address myAddress = this.getAddress(); - Deque stack = new ArrayDeque<>(); - - stack.offer("[cell, " + a.toHex(false) + "]"); - stack.offer("[cell, " + b.toHex(false) + "]"); - RunResult result = tonlib.runMethod(myAddress, "merge_inner_queries", stack); - - if (result.getExit_code() != 0) { - throw new Error("method merge_inner_queries, returned an exit code " + result.getExit_code()); - } - - TvmStackEntryCell entryCell = (TvmStackEntryCell) result.getStack().get(0); - - return CellBuilder.beginCell().fromBocBase64(entryCell.getCell().getBytes()).endCell(); - } - - /** - * Returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) - * - * @return Long status - */ - public long processed(Tonlib tonlib, BigInteger queryId) { - - Address myAddress = this.getAddress(); - - Deque stack = new ArrayDeque<>(); - - stack.offer("[num, " + queryId.toString(10) + "]"); - - RunResult result = tonlib.runMethod(myAddress, "processed?", stack); - - if (result.getExit_code() != 0) { - throw new Error("method processed, returned an exit code " + result.getExit_code()); - } - TvmStackEntryNumber cnt = (TvmStackEntryNumber) result.getStack().get(0); - return cnt.getNumber().longValue(); + if (result.getExit_code() != 0) { + throw new Error("method processed, returned an exit code " + result.getExit_code()); } -} \ No newline at end of file + TvmStackEntryNumber cnt = (TvmStackEntryNumber) result.getStack().get(0); + return cnt.getNumber().longValue(); + } +} diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinter.java b/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinter.java index 15cc5e86..e92867e5 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinter.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinter.java @@ -1,9 +1,15 @@ package org.ton.java.smartcontract.token.ft; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + import com.iwebpp.crypto.TweetNaclFast; +import java.math.BigInteger; +import java.util.ArrayDeque; +import java.util.Deque; import lombok.Builder; import lombok.Getter; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.address.Address; import org.ton.java.cell.Cell; @@ -19,16 +25,9 @@ import org.ton.java.tonlib.types.TvmStackEntrySlice; import org.ton.java.utils.Utils; -import java.math.BigInteger; -import java.util.ArrayDeque; -import java.util.Deque; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - @Builder @Getter -@Log +@Slf4j public class JettonMinter implements Contract { TweetNaclFast.Signature.KeyPair keyPair; Address adminAddress; diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinterStableCoin.java b/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinterStableCoin.java index e9255b65..a8d601fa 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinterStableCoin.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/token/ft/JettonMinterStableCoin.java @@ -1,9 +1,15 @@ package org.ton.java.smartcontract.token.ft; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + import com.iwebpp.crypto.TweetNaclFast; +import java.math.BigInteger; +import java.util.ArrayDeque; +import java.util.Deque; import lombok.Builder; import lombok.Getter; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.address.Address; import org.ton.java.cell.Cell; @@ -19,268 +25,274 @@ import org.ton.java.tonlib.types.TvmStackEntrySlice; import org.ton.java.utils.Utils; -import java.math.BigInteger; -import java.util.ArrayDeque; -import java.util.Deque; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - @Builder @Getter -@Log +@Slf4j public class JettonMinterStableCoin implements Contract { - TweetNaclFast.Signature.KeyPair keyPair; - Address adminAddress; - Address nextAdminAddress; - Cell content; - String jettonWalletCodeHex; - Address customAddress; - String code; - - public static class JettonMinterStableCoinBuilder { - } - - public static JettonMinterStableCoinBuilder builder() { - return new CustomJettonMinterStableCoinBuilder(); - } - - private static class CustomJettonMinterStableCoinBuilder extends JettonMinterStableCoinBuilder { - @Override - public JettonMinterStableCoin build() { - if (isNull(super.keyPair)) { - super.keyPair = Utils.generateSignatureKeyPair(); - } - - return super.build(); - } - } - - private Tonlib tonlib; - private long wc; - - @Override - public Tonlib getTonlib() { - return tonlib; - } + TweetNaclFast.Signature.KeyPair keyPair; + Address adminAddress; + Address nextAdminAddress; + Cell content; + String jettonWalletCodeHex; + Address customAddress; + String code; - @Override - public long getWorkchain() { - return wc; - } + public static class JettonMinterStableCoinBuilder {} - public String getName() { - return "jettonMinterStableCoin"; - } + public static JettonMinterStableCoinBuilder builder() { + return new CustomJettonMinterStableCoinBuilder(); + } - /** - * @return Cell cell - contains jetton data cell - */ + private static class CustomJettonMinterStableCoinBuilder extends JettonMinterStableCoinBuilder { @Override - public Cell createDataCell() { - if (StringUtils.isNotEmpty(code)) { - log.info("Using custom JettonMinter"); - return CellBuilder.beginCell() - .storeCoins(BigInteger.ZERO) - .storeAddress(adminAddress) - .storeAddress(nextAdminAddress) - .storeRef(CellBuilder.beginCell().fromBoc(code).endCell()) - .storeRef(content) -// .storeRef(NftUtils.createOnchainDataCell(jettonContentUri, 6L)) - .endCell(); - } else { - return CellBuilder.beginCell() - .storeCoins(BigInteger.ZERO) - .storeAddress(adminAddress) - .storeAddress(nextAdminAddress) - .storeRef(CellBuilder.beginCell().fromBoc(WalletCodes.jettonMinterStableCoin.getValue()).endCell()) - .storeRef(content) -// .storeRef(NftUtils.createOnchainDataCell(jettonContentUri, 6L)) - .endCell(); - } - } + public JettonMinterStableCoin build() { + if (isNull(super.keyPair)) { + super.keyPair = Utils.generateSignatureKeyPair(); + } - @Override - public Cell createCodeCell() { - if (StringUtils.isNotEmpty(code)) { - log.info("Using custom JettonMinter"); - return CellBuilder.beginCell(). - fromBoc(code). - endCell(); - } - return CellBuilder.beginCell(). - fromBoc(WalletCodes.jettonMinterStableCoin.getValue()) - .endCell(); + return super.build(); } - - /** - * @param queryId long - * @param destination Address - * @param amount BigInteger - * @param jettonAmount BigInteger - * @param fromAddress Address - * @param responseAddress Address - * @param forwardAmount BigInteger - * @return Cell - */ - public static Cell createMintBody(long queryId, Address destination, BigInteger amount, - BigInteger jettonAmount, Address fromAddress, Address - responseAddress, BigInteger forwardAmount, - Cell forwardPayload) { - return CellBuilder.beginCell() - .storeUint(0x642b7d07, 32) - .storeUint(queryId, 64) - .storeAddress(destination) - .storeCoins(amount) - .storeRef(CellBuilder.beginCell() // internal transfer - .storeUint(0x178d4519, 32) // internal_transfer op - .storeUint(queryId, 64) // default 0 - .storeCoins(jettonAmount) - .storeAddress(fromAddress) // from_address - .storeAddress(responseAddress) // response_address - .storeCoins(forwardAmount) // forward_amount - .storeBit(false) // forward payload - // forward_payload in this slice, not separate cell - .storeCell(forwardPayload) - .endCell()) - .endCell(); + } + + private Tonlib tonlib; + private long wc; + + @Override + public Tonlib getTonlib() { + return tonlib; + } + + @Override + public long getWorkchain() { + return wc; + } + + public String getName() { + return "jettonMinterStableCoin"; + } + + /** + * @return Cell cell - contains jetton data cell + */ + @Override + public Cell createDataCell() { + if (StringUtils.isNotEmpty(code)) { + log.info("Using custom JettonMinter"); + return CellBuilder.beginCell() + .storeCoins(BigInteger.ZERO) + .storeAddress(adminAddress) + .storeAddress(nextAdminAddress) + .storeRef(CellBuilder.beginCell().fromBoc(code).endCell()) + .storeRef(content) + // .storeRef(NftUtils.createOnchainDataCell(jettonContentUri, 6L)) + .endCell(); + } else { + return CellBuilder.beginCell() + .storeCoins(BigInteger.ZERO) + .storeAddress(adminAddress) + .storeAddress(nextAdminAddress) + .storeRef( + CellBuilder.beginCell() + .fromBoc(WalletCodes.jettonMinterStableCoin.getValue()) + .endCell()) + .storeRef(content) + // .storeRef(NftUtils.createOnchainDataCell(jettonContentUri, 6L)) + .endCell(); } + } - /** - * @param queryId long - * @param newAdminAddress Address - * @return Cell - */ - public static Cell createChangeAdminBody(long queryId, Address newAdminAddress) { - if (isNull(newAdminAddress)) { - throw new Error("Specify newAdminAddress"); - } - - return CellBuilder.beginCell() - .storeUint(0x6501f354, 32) - .storeUint(queryId, 64) - .storeAddress(newAdminAddress) - .endCell(); + @Override + public Cell createCodeCell() { + if (StringUtils.isNotEmpty(code)) { + log.info("Using custom JettonMinter"); + return CellBuilder.beginCell().fromBoc(code).endCell(); } - - public static Cell createUpgradeBody(long queryId, Cell data, Cell code) { - return CellBuilder.beginCell() - .storeUint(0x2508d66a, 32) - .storeUint(queryId, 64) - .storeRef(data) - .storeRef(code) - .endCell(); + return CellBuilder.beginCell().fromBoc(WalletCodes.jettonMinterStableCoin.getValue()).endCell(); + } + + /** + * @param queryId long + * @param destination Address + * @param amount BigInteger + * @param jettonAmount BigInteger + * @param fromAddress Address + * @param responseAddress Address + * @param forwardAmount BigInteger + * @return Cell + */ + public static Cell createMintBody( + long queryId, + Address destination, + BigInteger amount, + BigInteger jettonAmount, + Address fromAddress, + Address responseAddress, + BigInteger forwardAmount, + Cell forwardPayload) { + return CellBuilder.beginCell() + .storeUint(0x642b7d07, 32) + .storeUint(queryId, 64) + .storeAddress(destination) + .storeCoins(amount) + .storeRef( + CellBuilder.beginCell() // internal transfer + .storeUint(0x178d4519, 32) // internal_transfer op + .storeUint(queryId, 64) // default 0 + .storeCoins(jettonAmount) + .storeAddress(fromAddress) // from_address + .storeAddress(responseAddress) // response_address + .storeCoins(forwardAmount) // forward_amount + .storeBit(false) // forward payload + // forward_payload in this slice, not separate cell + .storeCell(forwardPayload) + .endCell()) + .endCell(); + } + + /** + * @param queryId long + * @param newAdminAddress Address + * @return Cell + */ + public static Cell createChangeAdminBody(long queryId, Address newAdminAddress) { + if (isNull(newAdminAddress)) { + throw new Error("Specify newAdminAddress"); } - /** - * @param jettonContentUri: String - * @param queryId long - * @return Cell - */ - public static Cell createChangeMetaDataUriBody(String jettonContentUri, long queryId) { - return CellBuilder.beginCell() - .storeUint(0xcb862902, 32) - .storeUint(queryId, 64) - .storeRef(NftUtils.createOffChainUriCell(jettonContentUri)) - .endCell(); + return CellBuilder.beginCell() + .storeUint(0x6501f354, 32) + .storeUint(queryId, 64) + .storeAddress(newAdminAddress) + .endCell(); + } + + public static Cell createUpgradeBody(long queryId, Cell data, Cell code) { + return CellBuilder.beginCell() + .storeUint(0x2508d66a, 32) + .storeUint(queryId, 64) + .storeRef(data) + .storeRef(code) + .endCell(); + } + + /** + * @param jettonContentUri: String + * @param queryId long + * @return Cell + */ + public static Cell createChangeMetaDataUriBody(String jettonContentUri, long queryId) { + return CellBuilder.beginCell() + .storeUint(0xcb862902, 32) + .storeUint(queryId, 64) + .storeRef(NftUtils.createOffChainUriCell(jettonContentUri)) + .endCell(); + } + + public static Cell createClaimAdminBody(long queryId) { + return CellBuilder.beginCell().storeUint(0xfb88e119, 32).storeUint(queryId, 64).endCell(); + } + + public static Cell createCallToBody( + long queryId, Address toAddress, BigInteger tonAmount, Cell masterMsg) { + return CellBuilder.beginCell() + .storeUint(0x235caf52, 32) + .storeUint(queryId, 64) + .storeAddress(toAddress) + .storeCoins(tonAmount) + .storeRef(masterMsg) + .endCell(); + } + + /** + * @return JettonData + */ + public JettonMinterData getJettonData() { + RunResult result; + if (nonNull(customAddress)) { + result = tonlib.runMethod(customAddress, "get_jetton_data"); + } else { + result = tonlib.runMethod(getAddress(), "get_jetton_data"); } - public static Cell createClaimAdminBody(long queryId) { - return CellBuilder.beginCell() - .storeUint(0xfb88e119, 32) - .storeUint(queryId, 64) - .endCell(); + if (result.getExit_code() != 0) { + throw new Error("method get_jetton_data, returned an exit code " + result.getExit_code()); } - public static Cell createCallToBody(long queryId, Address toAddress, BigInteger tonAmount, Cell masterMsg) { - return CellBuilder.beginCell() - .storeUint(0x235caf52, 32) - .storeUint(queryId, 64) - .storeAddress(toAddress) - .storeCoins(tonAmount) - .storeRef(masterMsg) - .endCell(); + TvmStackEntryNumber totalSupplyNumber = (TvmStackEntryNumber) result.getStack().get(0); + BigInteger totalSupply = totalSupplyNumber.getNumber(); + + boolean isMutable = + ((TvmStackEntryNumber) result.getStack().get(1)).getNumber().longValue() == -1; + + TvmStackEntrySlice adminAddr = (TvmStackEntrySlice) result.getStack().get(2); + Address adminAddress = + NftUtils.parseAddress( + CellBuilder.beginCell() + .fromBoc(Utils.base64ToBytes(adminAddr.getSlice().getBytes())) + .endCell()); + + TvmStackEntryCell jettonContent = (TvmStackEntryCell) result.getStack().get(3); + Cell jettonContentCell = + CellBuilder.beginCell() + .fromBoc(Utils.base64ToBytes(jettonContent.getCell().getBytes())) + .endCell(); + String jettonContentUri = null; + + jettonContentUri = NftUtils.parseOnChainUriCell(jettonContentCell); + + TvmStackEntryCell contentC = (TvmStackEntryCell) result.getStack().get(4); + Cell jettonWalletCode = + CellBuilder.beginCell() + .fromBoc(Utils.base64ToBytes(contentC.getCell().getBytes())) + .endCell(); + + return JettonMinterData.builder() + .totalSupply(totalSupply) + .isMutable(isMutable) + .adminAddress(adminAddress) + .jettonContentCell(jettonContentCell) + .jettonContentUri(jettonContentUri) + .jettonWalletCode(jettonWalletCode) + .build(); + } + + public BigInteger getTotalSupply() { + RunResult result; + if (nonNull(customAddress)) { + result = tonlib.runMethod(customAddress, "get_jetton_data"); // minter + } else { + result = tonlib.runMethod(getAddress(), "get_jetton_data"); // minter } - /** - * @return JettonData - */ - public JettonMinterData getJettonData() { - RunResult result; - if (nonNull(customAddress)) { - result = tonlib.runMethod(customAddress, "get_jetton_data"); - } else { - result = tonlib.runMethod(getAddress(), "get_jetton_data"); - } - - if (result.getExit_code() != 0) { - throw new Error("method get_jetton_data, returned an exit code " + result.getExit_code()); - } - - TvmStackEntryNumber totalSupplyNumber = (TvmStackEntryNumber) result.getStack().get(0); - BigInteger totalSupply = totalSupplyNumber.getNumber(); + TvmStackEntryNumber totalSupplyNumber = (TvmStackEntryNumber) result.getStack().get(0); + return totalSupplyNumber.getNumber(); + } - boolean isMutable = ((TvmStackEntryNumber) result.getStack().get(1)).getNumber().longValue() == -1; + public JettonWalletStableCoin getJettonWallet(Address ownerAddress) { + CellBuilder cell = CellBuilder.beginCell(); + cell.storeAddress(ownerAddress); - TvmStackEntrySlice adminAddr = (TvmStackEntrySlice) result.getStack().get(2); - Address adminAddress = NftUtils.parseAddress(CellBuilder.beginCell().fromBoc(Utils.base64ToBytes(adminAddr.getSlice().getBytes())).endCell()); + Deque stack = new ArrayDeque<>(); - TvmStackEntryCell jettonContent = (TvmStackEntryCell) result.getStack().get(3); - Cell jettonContentCell = CellBuilder.beginCell().fromBoc(Utils.base64ToBytes(jettonContent.getCell().getBytes())).endCell(); - String jettonContentUri = null; + stack.offer("[slice, " + cell.endCell().toHex(true) + "]"); - jettonContentUri = NftUtils.parseOnChainUriCell(jettonContentCell); - - - TvmStackEntryCell contentC = (TvmStackEntryCell) result.getStack().get(4); - Cell jettonWalletCode = CellBuilder.beginCell().fromBoc(Utils.base64ToBytes(contentC.getCell().getBytes())).endCell(); - - return JettonMinterData.builder() - .totalSupply(totalSupply) - .isMutable(isMutable) - .adminAddress(adminAddress) - .jettonContentCell(jettonContentCell) - .jettonContentUri(jettonContentUri) - .jettonWalletCode(jettonWalletCode) - .build(); + RunResult result; + if (nonNull(customAddress)) { + result = tonlib.runMethod(customAddress, "get_wallet_address", stack); + } else { + result = tonlib.runMethod(getAddress(), "get_wallet_address", stack); } - - public BigInteger getTotalSupply() { - RunResult result; - if (nonNull(customAddress)) { - result = tonlib.runMethod(customAddress, "get_jetton_data"); // minter - } else { - result = tonlib.runMethod(getAddress(), "get_jetton_data"); // minter - } - - TvmStackEntryNumber totalSupplyNumber = (TvmStackEntryNumber) result.getStack().get(0); - return totalSupplyNumber.getNumber(); + if (result.getExit_code() != 0) { + throw new Error("method get_wallet_address, returned an exit code " + result.getExit_code()); } - public JettonWalletStableCoin getJettonWallet(Address ownerAddress) { - CellBuilder cell = CellBuilder.beginCell(); - cell.storeAddress(ownerAddress); + TvmStackEntrySlice addr = (TvmStackEntrySlice) result.getStack().get(0); + Address jettonWalletAddress = + NftUtils.parseAddress( + CellBuilder.beginCell() + .fromBoc(Utils.base64ToBytes(addr.getSlice().getBytes())) + .endCell()); - Deque stack = new ArrayDeque<>(); - - stack.offer("[slice, " + cell.endCell().toHex(true) + "]"); - - RunResult result; - if (nonNull(customAddress)) { - result = tonlib.runMethod(customAddress, "get_wallet_address", stack); - } else { - result = tonlib.runMethod(getAddress(), "get_wallet_address", stack); - } - if (result.getExit_code() != 0) { - throw new Error("method get_wallet_address, returned an exit code " + result.getExit_code()); - } - - TvmStackEntrySlice addr = (TvmStackEntrySlice) result.getStack().get(0); - Address jettonWalletAddress = NftUtils.parseAddress(CellBuilder.beginCell().fromBoc(Utils.base64ToBytes(addr.getSlice().getBytes())).endCell()); - - return JettonWalletStableCoin.builder() - .tonlib(tonlib) - .address(jettonWalletAddress) - .build(); - } -} \ No newline at end of file + return JettonWalletStableCoin.builder().tonlib(tonlib).address(jettonWalletAddress).build(); + } +} diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/token/nft/NftUtils.java b/smartcontract/src/main/java/org/ton/java/smartcontract/token/nft/NftUtils.java index 0af9d20d..7c655585 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/token/nft/NftUtils.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/token/nft/NftUtils.java @@ -1,6 +1,11 @@ package org.ton.java.smartcontract.token.nft; -import lombok.extern.java.Log; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.address.Address; import org.ton.java.cell.Cell; @@ -14,152 +19,145 @@ import org.ton.java.tonlib.types.TvmStackEntrySlice; import org.ton.java.utils.Utils; -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -@Log +@Slf4j public class NftUtils { - - public static final int SNAKE_DATA_PREFIX = 0x00; - public static final int CHUNK_DATA_PREFIX = 0x01; - public static final int ONCHAIN_CONTENT_PREFIX = 0x00; - public static final int OFFCHAIN_CONTENT_PREFIX = 0x01; - - /** - * @param uri String - * @return byte[] - */ - public static byte[] serializeUri(String uri) { - try { - return URLEncoder.encode(uri, String.valueOf(StandardCharsets.UTF_8)).getBytes(); - } catch (Exception e) { - throw new Error("Cannot serialize URI " + uri); - } + public static final int SNAKE_DATA_PREFIX = 0x00; + public static final int CHUNK_DATA_PREFIX = 0x01; + public static final int ONCHAIN_CONTENT_PREFIX = 0x00; + public static final int OFFCHAIN_CONTENT_PREFIX = 0x01; + + /** + * @param uri String + * @return byte[] + */ + public static byte[] serializeUri(String uri) { + try { + return URLEncoder.encode(uri, String.valueOf(StandardCharsets.UTF_8)).getBytes(); + } catch (Exception e) { + throw new Error("Cannot serialize URI " + uri); } - - /** - * @param uri - * @return String - */ - static String parseUri(String uri) { - try { - return URLDecoder.decode(uri, String.valueOf(StandardCharsets.UTF_8)); - } catch (UnsupportedEncodingException e) { - log.info(e.getMessage()); - return null; - } - } - - /** - * @param uri String - * @return Cell - */ - public static Cell createOffChainUriCell(String uri) { - return CellBuilder.beginCell() - .storeUint(OFFCHAIN_CONTENT_PREFIX, 8) - .storeSnakeString(uri) - .endCell(); - } - - /** - * @param cell Cell - * @return String - */ - public static String parseOffChainUriCell(Cell cell) { - if ((cell.getBits().toByteArray()[0] & 0xFF) != OFFCHAIN_CONTENT_PREFIX) { - throw new Error("not OFFCHAIN_CONTENT_PREFIX"); - } - - return parseUri(CellSlice.beginParse(cell).skipBits(8).loadSnakeString()); + } + + /** + * @param uri + * @return String + */ + static String parseUri(String uri) { + try { + return URLDecoder.decode(uri, String.valueOf(StandardCharsets.UTF_8)); + } catch (UnsupportedEncodingException e) { + log.info(e.getMessage()); + return null; } - - public static String parseOnChainUriCell(Cell cell) { - - if ((cell.getBits().toByteArray()[0] & 0xFF) != ONCHAIN_CONTENT_PREFIX) { - throw new Error("not ONCHAIN_CONTENT_PREFIX"); - } - - CellSlice cs = CellSlice.beginParse(cell); - cs.skipBits(8); - - TonHashMapE loadedDict = cs - .loadDictE(256, - k -> k.readUint(256), - v -> CellSlice.beginParse(v).loadSnakeString() - ); - BigInteger key = new BigInteger(Utils.sha256("uri".getBytes()), 16); - String uri = loadedDict.elements.get(key).toString(); - return StringUtils.trim(uri); + } + + /** + * @param uri String + * @return Cell + */ + public static Cell createOffChainUriCell(String uri) { + return CellBuilder.beginCell() + .storeUint(OFFCHAIN_CONTENT_PREFIX, 8) + .storeSnakeString(uri) + .endCell(); + } + + /** + * @param cell Cell + * @return String + */ + public static String parseOffChainUriCell(Cell cell) { + if ((cell.getBits().toByteArray()[0] & 0xFF) != OFFCHAIN_CONTENT_PREFIX) { + throw new Error("not OFFCHAIN_CONTENT_PREFIX"); } - /** - * The first byte is 0x00 and the rest is key/value dictionary. - * Key is sha256 hash of string. - * Value is data encoded as described in "Data serialization" paragraph. - * - * @param uri - * @return Cell - */ - public static Cell createOnChainDataCell(String uri, Long decimals) { - // https://github.com/ton-blockchain/TIPs/issues/64 - CellBuilder cell = CellBuilder.beginCell(); - cell.storeUint(ONCHAIN_CONTENT_PREFIX, 8); - - int keySizeX = 256; - TonHashMapE x = new TonHashMapE(keySizeX); - - BigInteger uriKey = new BigInteger(Utils.sha256("uri".getBytes()), 16); - x.elements.put(uriKey, CellBuilder.beginCell().storeSnakeString(uri).endCell()); - BigInteger decimalsKey = new BigInteger(Utils.sha256("decimals".getBytes()), 16); - - x.elements.put(uriKey, CellBuilder.beginCell().storeSnakeString(uri).endCell()); - x.elements.put(decimalsKey, CellBuilder.beginCell().storeString(decimals.toString()).endCell()); - - Cell cellDict = x.serialize( - k -> CellBuilder.beginCell().storeUint((BigInteger) k, keySizeX).endCell().getBits(), - v -> CellBuilder.beginCell().storeCell((Cell) v).endCell() - ); - - cell.storeDict(cellDict); - return cell.endCell(); - } + return parseUri(CellSlice.beginParse(cell).skipBits(8).loadSnakeString()); + } - /** - * @param cell Cell - * @return Address|null - */ - public static Address parseAddress(Cell cell) { + public static String parseOnChainUriCell(Cell cell) { - return CellSlice.beginParse(cell).loadAddress(); + if ((cell.getBits().toByteArray()[0] & 0xFF) != ONCHAIN_CONTENT_PREFIX) { + throw new Error("not ONCHAIN_CONTENT_PREFIX"); } - /** - * @param tonlib Tonlib - * @param address String - * @return Royalty - */ - public static Royalty getRoyaltyParams(Tonlib tonlib, Address address) { - RunResult result = tonlib.runMethod(address, "royalty_params"); - - TvmStackEntryNumber royaltyFactorNumber = (TvmStackEntryNumber) result.getStack().get(0); - BigInteger royaltyFactor = royaltyFactorNumber.getNumber(); - - TvmStackEntryNumber royaltyBaseNumber = (TvmStackEntryNumber) result.getStack().get(1); - BigInteger royaltyBase = royaltyBaseNumber.getNumber(); - - double royalty = royaltyFactor.divide(royaltyBase).doubleValue(); - TvmStackEntrySlice royaltyAddressCell = (TvmStackEntrySlice) result.getStack().get(2); - Address royaltyAddress = NftUtils.parseAddress(CellBuilder.beginCell().fromBoc(Utils.base64ToBytes(royaltyAddressCell.getSlice().getBytes())).endCell()); - - return Royalty.builder() - .royaltyFactor(royaltyFactor) - .royaltyBase(royaltyBase) - .royalty(royalty) - .royaltyAddress(royaltyAddress) - .build(); - } + CellSlice cs = CellSlice.beginParse(cell); + cs.skipBits(8); + + TonHashMapE loadedDict = + cs.loadDictE(256, k -> k.readUint(256), v -> CellSlice.beginParse(v).loadSnakeString()); + BigInteger key = new BigInteger(Utils.sha256("uri".getBytes()), 16); + String uri = loadedDict.elements.get(key).toString(); + return StringUtils.trim(uri); + } + + /** + * The first byte is 0x00 and the rest is key/value dictionary. Key is sha256 hash of string. + * Value is data encoded as described in "Data serialization" paragraph. + * + * @param uri + * @return Cell + */ + public static Cell createOnChainDataCell(String uri, Long decimals) { + // https://github.com/ton-blockchain/TIPs/issues/64 + CellBuilder cell = CellBuilder.beginCell(); + cell.storeUint(ONCHAIN_CONTENT_PREFIX, 8); + + int keySizeX = 256; + TonHashMapE x = new TonHashMapE(keySizeX); + + BigInteger uriKey = new BigInteger(Utils.sha256("uri".getBytes()), 16); + x.elements.put(uriKey, CellBuilder.beginCell().storeSnakeString(uri).endCell()); + BigInteger decimalsKey = new BigInteger(Utils.sha256("decimals".getBytes()), 16); + + x.elements.put(uriKey, CellBuilder.beginCell().storeSnakeString(uri).endCell()); + x.elements.put(decimalsKey, CellBuilder.beginCell().storeString(decimals.toString()).endCell()); + + Cell cellDict = + x.serialize( + k -> CellBuilder.beginCell().storeUint((BigInteger) k, keySizeX).endCell().getBits(), + v -> CellBuilder.beginCell().storeCell((Cell) v).endCell()); + + cell.storeDict(cellDict); + return cell.endCell(); + } + + /** + * @param cell Cell + * @return Address|null + */ + public static Address parseAddress(Cell cell) { + + return CellSlice.beginParse(cell).loadAddress(); + } + + /** + * @param tonlib Tonlib + * @param address String + * @return Royalty + */ + public static Royalty getRoyaltyParams(Tonlib tonlib, Address address) { + RunResult result = tonlib.runMethod(address, "royalty_params"); + + TvmStackEntryNumber royaltyFactorNumber = (TvmStackEntryNumber) result.getStack().get(0); + BigInteger royaltyFactor = royaltyFactorNumber.getNumber(); + + TvmStackEntryNumber royaltyBaseNumber = (TvmStackEntryNumber) result.getStack().get(1); + BigInteger royaltyBase = royaltyBaseNumber.getNumber(); + + double royalty = royaltyFactor.divide(royaltyBase).doubleValue(); + TvmStackEntrySlice royaltyAddressCell = (TvmStackEntrySlice) result.getStack().get(2); + Address royaltyAddress = + NftUtils.parseAddress( + CellBuilder.beginCell() + .fromBoc(Utils.base64ToBytes(royaltyAddressCell.getSlice().getBytes())) + .endCell()); + + return Royalty.builder() + .royaltyFactor(royaltyFactor) + .royaltyBase(royaltyBase) + .royalty(royalty) + .royaltyAddress(royaltyAddress) + .build(); + } } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java index ec759da9..304b3623 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java @@ -1,7 +1,7 @@ package org.ton.java.smartcontract.wallet; - -import lombok.extern.java.Log; +import java.math.BigInteger; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.address.Address; import org.ton.java.smartcontract.token.ft.JettonMinter; @@ -9,70 +9,65 @@ import org.ton.java.tonlib.Tonlib; import org.ton.java.utils.Utils; -import java.math.BigInteger; - -@Log +@Slf4j public class ContractUtils { - public static long getSeqno(Tonlib tonlib, Address address) { - return tonlib.getSeqno(address); - } + public static long getSeqno(Tonlib tonlib, Address address) { + return tonlib.getSeqno(address); + } - public static boolean isDeployed(Tonlib tonlib, Address address) { - return StringUtils.isNotEmpty(tonlib.getRawAccountState(address).getCode()); - } + public static boolean isDeployed(Tonlib tonlib, Address address) { + return StringUtils.isNotEmpty(tonlib.getRawAccountState(address).getCode()); + } - public static void waitForDeployment(Tonlib tonlib, Address address, int timeoutSeconds) { - log.info("waiting for deployment up to " + timeoutSeconds + " sec"); - int i = 0; - do { - if (++i * 2 >= timeoutSeconds) { - throw new Error("Can't deploy contract within specified timeout."); - } - Utils.sleep(2); - } - while (!isDeployed(tonlib, address)); - } + public static void waitForDeployment(Tonlib tonlib, Address address, int timeoutSeconds) { + log.info("waiting for deployment up to " + timeoutSeconds + " sec"); + int i = 0; + do { + if (++i * 2 >= timeoutSeconds) { + throw new Error("Can't deploy contract within specified timeout."); + } + Utils.sleep(2); + } while (!isDeployed(tonlib, address)); + } - public static void waitForBalanceChange(Tonlib tonlib, Address address, int timeoutSeconds) { - log.info("waiting for balance change up to " + timeoutSeconds + " sec"); - BigInteger initialBalance = tonlib.getAccountBalance(address); - int i = 0; - do { - if (++i * 2 >= timeoutSeconds) { - throw new Error("Balance was not changed within specified timeout."); - } - Utils.sleep(2); - } - while (initialBalance.equals(tonlib.getAccountBalance(address))); - } + public static void waitForBalanceChange(Tonlib tonlib, Address address, int timeoutSeconds) { + log.info("waiting for balance change up to " + timeoutSeconds + " sec"); + BigInteger initialBalance = tonlib.getAccountBalance(address); + int i = 0; + do { + if (++i * 2 >= timeoutSeconds) { + throw new Error("Balance was not changed within specified timeout."); + } + Utils.sleep(2); + } while (initialBalance.equals(tonlib.getAccountBalance(address))); + } - public static void waitForJettonBalanceChange(Tonlib tonlib, Address jettonMinter, Address address, int timeoutSeconds) { - log.info("waiting for jetton balance change up to " + timeoutSeconds + " sec"); - BigInteger initialBalance = getJettonBalance(tonlib, jettonMinter, address); - int i = 0; - do { - if (++i * 2 >= timeoutSeconds) { - throw new Error("Balance was not changed within specified timeout."); - } - Utils.sleep(2); - } - while (initialBalance.equals(getJettonBalance(tonlib, jettonMinter, address))); - } + public static void waitForJettonBalanceChange( + Tonlib tonlib, Address jettonMinter, Address address, int timeoutSeconds) { + log.info("waiting for jetton balance change up to " + timeoutSeconds + " sec"); + BigInteger initialBalance = getJettonBalance(tonlib, jettonMinter, address); + int i = 0; + do { + if (++i * 2 >= timeoutSeconds) { + throw new Error("Balance was not changed within specified timeout."); + } + Utils.sleep(2); + } while (initialBalance.equals(getJettonBalance(tonlib, jettonMinter, address))); + } - public static BigInteger getJettonBalance(Tonlib tonlib, Address jettonMinter, Address destinationAddress) { + public static BigInteger getJettonBalance( + Tonlib tonlib, Address jettonMinter, Address destinationAddress) { - try { - JettonMinter jettonMinterWallet = JettonMinter.builder() - .tonlib(tonlib) - .customAddress(jettonMinter) - .build(); + try { + JettonMinter jettonMinterWallet = + JettonMinter.builder().tonlib(tonlib).customAddress(jettonMinter).build(); - JettonWallet jettonWallet = jettonMinterWallet.getJettonWallet(destinationAddress); + JettonWallet jettonWallet = jettonMinterWallet.getJettonWallet(destinationAddress); - return jettonWallet.getBalance(); - } catch (Error e) { - return new BigInteger("-1"); - } + return jettonWallet.getBalance(); + } catch (Error e) { + return new BigInteger("-1"); } -} \ No newline at end of file + } +} diff --git a/tonlib/pom.xml b/tonlib/pom.xml index a6e21eb9..2e49ed07 100644 --- a/tonlib/pom.xml +++ b/tonlib/pom.xml @@ -100,7 +100,6 @@ ch.qos.logback logback-classic - test org.assertj diff --git a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java index 824a9e9a..eae85a64 100644 --- a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java +++ b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java @@ -14,7 +14,7 @@ import java.util.*; import java.util.concurrent.TimeUnit; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.address.Address; @@ -25,7 +25,7 @@ import org.ton.java.tonlib.types.globalconfig.*; import org.ton.java.utils.Utils; -@Log +@Slf4j @Builder public class Tonlib { diff --git a/tonlib/src/test/java/org/ton/java/tonlib/TestTonlibJson.java b/tonlib/src/test/java/org/ton/java/tonlib/TestTonlibJson.java index 80f0968e..ce00c20c 100644 --- a/tonlib/src/test/java/org/ton/java/tonlib/TestTonlibJson.java +++ b/tonlib/src/test/java/org/ton/java/tonlib/TestTonlibJson.java @@ -1,9 +1,21 @@ package org.ton.java.tonlib; +import static java.util.Objects.nonNull; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.iwebpp.crypto.TweetNaclFast; import com.sun.jna.Native; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -22,818 +34,927 @@ import org.ton.java.tonlib.types.globalconfig.*; import org.ton.java.utils.Utils; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayDeque; -import java.util.Collections; -import java.util.Deque; -import java.util.Map; - -import static java.util.Objects.nonNull; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - @Slf4j @RunWith(JUnit4.class) public class TestTonlibJson { - public static final String TON_FOUNDATION = "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"; - public static final String ELECTOR_ADDRESSS = "-1:3333333333333333333333333333333333333333333333333333333333333333"; - - Gson gs = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); - - static Tonlib tonlib; - - @BeforeClass - public static void setUpBeforeClass() { - tonlib = Tonlib.builder() - .receiveTimeout(5) - .ignoreCache(false).build(); - } - - @Test - public void testIssue13() { - Tonlib tonlib = Tonlib.builder().build(); - BlockIdExt block = tonlib.getLast().getLast(); - log.info("block {}", block); - } - - @Test - public void testInitTonlibJson() throws IOException { - TonlibJsonI tonlibJson = Native.load("tonlibjson.dll", TonlibJsonI.class); - - long tonlib = tonlibJson.tonlib_client_json_create(); - - InputStream testConfig = TestTonlibJson.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); - - assert nonNull(testConfig); - - String globalConfigJson = Utils.streamToString(testConfig); - testConfig.close(); - - assert nonNull(globalConfigJson); - - TonlibSetup tonlibSetup = TonlibSetup.builder() - .type("init") - .options(TonlibOptions.builder() - .type("options") - .config(TonlibConfig.builder() - .type("config") - .config(globalConfigJson) - .use_callbacks_for_network(false) - .blockchain_name("") - .ignore_cache(true) + public static final String TON_FOUNDATION = "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"; + public static final String ELECTOR_ADDRESSS = + "-1:3333333333333333333333333333333333333333333333333333333333333333"; + + Gson gs = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + + static Tonlib tonlib; + + @BeforeClass + public static void setUpBeforeClass() { + tonlib = Tonlib.builder().receiveTimeout(5).ignoreCache(false).build(); + } + + @Test + public void testIssue13() { + Tonlib tonlib = Tonlib.builder().build(); + BlockIdExt block = tonlib.getLast().getLast(); + log.info("block {}", block); + } + + @Test + public void testInitTonlibJson() throws IOException { + TonlibJsonI tonlibJson = Native.load("tonlibjson.dll", TonlibJsonI.class); + + long tonlib = tonlibJson.tonlib_client_json_create(); + + InputStream testConfig = + TestTonlibJson.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); + + assert nonNull(testConfig); + + String globalConfigJson = Utils.streamToString(testConfig); + testConfig.close(); + + assert nonNull(globalConfigJson); + + TonlibSetup tonlibSetup = + TonlibSetup.builder() + .type("init") + .options( + TonlibOptions.builder() + .type("options") + .config( + TonlibConfig.builder() + .type("config") + .config(globalConfigJson) + .use_callbacks_for_network(false) + .blockchain_name("") + .ignore_cache(true) + .build()) + .keystore_type( + KeyStoreTypeDirectory.builder() + .type("keyStoreTypeDirectory") + .directory(".") + .build()) + .build()) + .build(); + + tonlibJson.tonlib_client_json_send(tonlib, gs.toJson(tonlibSetup)); + String result = tonlibJson.tonlib_client_json_receive(tonlib, 10.0); + assertThat(result).isNotBlank(); + } + + @Test + public void testGlobalConfigJsonParser() throws IOException { + + InputStream testConfig = + TestTonlibJson.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); + String configStr = Utils.streamToString(testConfig); + assert testConfig != null; + testConfig.close(); + TonGlobalConfig globalConfig = gs.fromJson(configStr, TonGlobalConfig.class); + log.info("config object: {}", globalConfig); + + log.info("lite-servers found {}", globalConfig.getLiteservers().length); + log.info("dht-servers found {}", globalConfig.getDht().getStatic_nodes().getNodes().length); + log.info("hard-forks found {}", globalConfig.getValidator().getHardforks().length); + + log.info("parsed config object back to json {}", gs.toJson(globalConfig)); + + Tonlib tonlib1 = + Tonlib.builder().globalConfigAsString(gs.toJson(globalConfig)).ignoreCache(false).build(); + + log.info("last {}", tonlib1.getLast()); + } + + @Test + public void testManualGlobalConfig() { + + TonGlobalConfig testnetGlobalConfig = + TonGlobalConfig.builder() + .liteservers( + ArrayUtils.toArray( + LiteServers.builder() + .ip(1495755568) + .port(4695) + .id( + LiteServerId.builder() + .type("pub.ed25519") + .key("cZpMFqy6n0Lsu8x/z2Jq0wh/OdM1WAVJJKSb2CvDECQ=") .build()) - .keystore_type( - KeyStoreTypeDirectory.builder() - .type("keyStoreTypeDirectory") - .directory(".") - .build()) - .build()) - .build(); - - - tonlibJson.tonlib_client_json_send(tonlib, gs.toJson(tonlibSetup)); - String result = tonlibJson.tonlib_client_json_receive(tonlib, 10.0); - assertThat(result).isNotBlank(); - } - - @Test - public void testGlobalConfigJsonParser() throws IOException { - - InputStream testConfig = TestTonlibJson.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); - String configStr = Utils.streamToString(testConfig); - assert testConfig != null; - testConfig.close(); - TonGlobalConfig globalConfig = gs.fromJson(configStr, TonGlobalConfig.class); - log.info("config object: {}", globalConfig); - - log.info("lite-servers found {}", globalConfig.getLiteservers().length); - log.info("dht-servers found {}", globalConfig.getDht().getStatic_nodes().getNodes().length); - log.info("hard-forks found {}", globalConfig.getValidator().getHardforks().length); - - log.info("parsed config object back to json {}", gs.toJson(globalConfig)); - - Tonlib tonlib1 = Tonlib.builder() - .globalConfigAsString(gs.toJson(globalConfig)) - .ignoreCache(false) - .build(); - - log.info("last {}", tonlib1.getLast()); - } - - @Test - public void testManualGlobalConfig() { - - TonGlobalConfig testnetGlobalConfig = TonGlobalConfig.builder() - .liteservers(ArrayUtils.toArray( - LiteServers.builder() - .ip(1495755568) - .port(4695) - .id(LiteServerId.builder() - .type("pub.ed25519") - .key("cZpMFqy6n0Lsu8x/z2Jq0wh/OdM1WAVJJKSb2CvDECQ=") - .build()) + .build(), + LiteServers.builder() + .ip(1468571697) + .port(27787) + .id( + LiteServerId.builder() + .type("pub.ed25519") + .key("Y/QVf6G5VDiKTZOKitbFVm067WsuocTN8Vg036A4zGk=") + .build()) + .build())) + .validator( + Validator.builder() + .type("validator.config.global") + .zero_state( + BlockInfo.builder() + .file_hash("Z+IKwYS54DmmJmesw/nAD5DzWadnOCMzee+kdgSYDOg=") + .root_hash("gj+B8wb/AmlPk1z1AhVI484rhrUpgSr2oSFIh56VoSg=") + .workchain(-1) + .seqno(0) + .shard(-9223372036854775808L) + .build()) + .hardforks( + ArrayUtils.toArray( + BlockInfo.builder() + .file_hash("jF3RTD+OyOoP+OI9oIjdV6M8EaOh9E+8+c3m5JkPYdg=") + .root_hash("6JSqIYIkW7y8IorxfbQBoXiuY3kXjcoYgQOxTJpjXXA=") + .workchain(-1) + .seqno(5141579) + .shard(-9223372036854775808L) .build(), - LiteServers.builder() - .ip(1468571697) - .port(27787) - .id(LiteServerId.builder() - .type("pub.ed25519") - .key("Y/QVf6G5VDiKTZOKitbFVm067WsuocTN8Vg036A4zGk=") - .build()) - .build())) - .validator(Validator.builder() - .type("validator.config.global") - .zero_state(BlockInfo.builder() - .file_hash("Z+IKwYS54DmmJmesw/nAD5DzWadnOCMzee+kdgSYDOg=") - .root_hash("gj+B8wb/AmlPk1z1AhVI484rhrUpgSr2oSFIh56VoSg=") + BlockInfo.builder() + .file_hash("WrNoMrn5UIVPDV/ug/VPjYatvde8TPvz5v1VYHCLPh8=") + .root_hash("054VCNNtUEwYGoRe1zjH+9b1q21/MeM+3fOo76Vcjes=") .workchain(-1) - .seqno(0) + .seqno(5172980) .shard(-9223372036854775808L) - .build()) - .hardforks(ArrayUtils.toArray( - BlockInfo.builder() - .file_hash("jF3RTD+OyOoP+OI9oIjdV6M8EaOh9E+8+c3m5JkPYdg=") - .root_hash("6JSqIYIkW7y8IorxfbQBoXiuY3kXjcoYgQOxTJpjXXA=") - .workchain(-1) - .seqno(5141579) - .shard(-9223372036854775808L) - .build(), - BlockInfo.builder() - .file_hash("WrNoMrn5UIVPDV/ug/VPjYatvde8TPvz5v1VYHCLPh8=") - .root_hash("054VCNNtUEwYGoRe1zjH+9b1q21/MeM+3fOo76Vcjes=") - .workchain(-1) - .seqno(5172980) - .shard(-9223372036854775808L) - .build(), - BlockInfo.builder() - .file_hash("xRaxgUwgTXYFb16YnR+Q+VVsczLl6jmYwvzhQ/ncrh4=") - .root_hash("SoPLqMe9Dz26YJPOGDOHApTSe5i0kXFtRmRh/zPMGuI=") - .workchain(-1) - .seqno(5176527) - .shard(-9223372036854775808L) - .build() - )).build()) - .build(); - log.info("config object: {}", testnetGlobalConfig); - - log.info("lite-servers found {}", testnetGlobalConfig.getLiteservers().length); - - log.info("parsed config object back to json {}", gs.toJson(testnetGlobalConfig)); - - Tonlib tonlib1 = Tonlib.builder() - .globalConfig(testnetGlobalConfig) - .ignoreCache(false) - .build(); - - log.info("last {}", tonlib1.getLast()); - } + .build(), + BlockInfo.builder() + .file_hash("xRaxgUwgTXYFb16YnR+Q+VVsczLl6jmYwvzhQ/ncrh4=") + .root_hash("SoPLqMe9Dz26YJPOGDOHApTSe5i0kXFtRmRh/zPMGuI=") + .workchain(-1) + .seqno(5176527) + .shard(-9223372036854775808L) + .build())) + .build()) + .build(); + log.info("config object: {}", testnetGlobalConfig); - @Test - public void testTonlibUsingGlobalConfigLiteServerByIndex() { + log.info("lite-servers found {}", testnetGlobalConfig.getLiteservers().length); - Tonlib tonlib1 = Tonlib.builder() - .ignoreCache(false) - .testnet(true) - .liteServerIndex(3) - .build(); + log.info("parsed config object back to json {}", gs.toJson(testnetGlobalConfig)); - log.info("last {}", tonlib1.getLast()); - } + Tonlib tonlib1 = Tonlib.builder().globalConfig(testnetGlobalConfig).ignoreCache(false).build(); - @Test - public void testTonlib() { - Tonlib tonlib = Tonlib.builder() - .keystoreInMemory(true) - .build(); + log.info("last {}", tonlib1.getLast()); + } -// lookupBlock - BlockIdExt fullblock = tonlib.lookupBlock(23512606, -1, -9223372036854775808L, 0, 0); + @Test + public void testTonlibUsingGlobalConfigLiteServerByIndex() { - log.info(fullblock.toString()); + Tonlib tonlib1 = Tonlib.builder().ignoreCache(false).testnet(true).liteServerIndex(3).build(); - MasterChainInfo masterChainInfo = tonlib.getLast(); - log.info(masterChainInfo.toString()); + log.info("last {}", tonlib1.getLast()); + } - //getBlockHeader - BlockHeader header = tonlib.getBlockHeader(masterChainInfo.getLast()); - log.info(header.toString()); + @Test + public void testTonlib() { + Tonlib tonlib = Tonlib.builder().keystoreInMemory(true).build(); - //getShards - Shards shards1 = tonlib.getShards(masterChainInfo.getLast()); // only seqno also ok? - log.info(shards1.toString()); - assertThat(shards1.getShards()).isNotNull(); - } + // lookupBlock + BlockIdExt fullblock = tonlib.lookupBlock(23512606, -1, -9223372036854775808L, 0, 0); - @Test - public void testTonlibGetLast() { - Tonlib tonlib = Tonlib.builder() - .testnet(true) - .keystoreInMemory(true) - .build(); - BlockIdExt fullblock = tonlib.getLast().getLast(); - log.info("last {}", fullblock); - assertThat(fullblock).isNotNull(); - } + log.info(fullblock.toString()); - @Test - public void testTonlibGetAllBlockTransactions() { - BlockIdExt fullblock = tonlib.getLast().getLast(); - assertThat(fullblock).isNotNull(); - - log.info(fullblock.toString()); - - Map txs = tonlib.getAllBlockTransactions(fullblock, 100, null); - for (Map.Entry entry : txs.entrySet()) { - for (RawTransaction tx : entry.getValue().getTransactions()) { - if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - log.info("{} <<<<< {} : {} ", tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue(), 9)); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - log.info("{} >>>>> {} : {} ", msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue())); - } - } - } - } - assertThat(txs.size()).isNotEqualTo(0); - } + MasterChainInfo masterChainInfo = tonlib.getLast(); + log.info(masterChainInfo.toString()); - @Test - public void testTonlibGetBlockTransactions() { - for (int i = 0; i < 2; i++) { - - MasterChainInfo lastBlock = tonlib.getLast(); - log.info(lastBlock.toString()); - - BlockTransactions blockTransactions = tonlib.getBlockTransactions(lastBlock.getLast(), 100); - log.info(gs.toJson(blockTransactions)); - - for (ShortTxId shortTxId : blockTransactions.getTransactions()) { - Address acccount = Address.of("-1:" + Utils.base64ToHexString(shortTxId.getAccount())); - log.info("lt {}, hash {}, account {}", shortTxId.getLt(), shortTxId.getHash(), acccount.toString(false)); - RawTransactions rawTransactions = tonlib.getRawTransactions(acccount.toString(false), BigInteger.valueOf(shortTxId.getLt()), shortTxId.getHash()); - for (RawTransaction tx : rawTransactions.getTransactions()) { - if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - log.info("{}, {} <<<<< {} : {} ", Utils.toUTC(tx.getUtime()), tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue())); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - log.info("{}, {} >>>>> {} : {} ", Utils.toUTC(tx.getUtime()), msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue())); - } - } - } - } - Utils.sleep(10, "wait for next block"); - } - } + // getBlockHeader + BlockHeader header = tonlib.getBlockHeader(masterChainInfo.getLast()); + log.info(header.toString()); - @Test - public void testTonlibGetTxsByAddress() { - Address address = Address.of(TON_FOUNDATION); + // getShards + Shards shards1 = tonlib.getShards(masterChainInfo.getLast()); // only seqno also ok? + log.info(shards1.toString()); + assertThat(shards1.getShards()).isNotNull(); + } - log.info("address: " + address.toBounceable()); + @Test + public void testTonlibGetLast() { + Tonlib tonlib = Tonlib.builder().testnet(true).keystoreInMemory(true).build(); + BlockIdExt fullblock = tonlib.getLast().getLast(); + log.info("last {}", fullblock); + assertThat(fullblock).isNotNull(); + } - RawTransactions rawTransactions = tonlib.getRawTransactions(address.toRaw(), null, null); + @Test + public void testTonlibGetAllBlockTransactions() { + BlockIdExt fullblock = tonlib.getLast().getLast(); + assertThat(fullblock).isNotNull(); - log.info("total txs: {}", rawTransactions.getTransactions().size()); + log.info(fullblock.toString()); - for (RawTransaction tx : rawTransactions.getTransactions()) { - if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - log.info("{}, {} <<<<< {} : {} ", Utils.toUTC(tx.getUtime()), tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue())); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - log.info("{}, {} >>>>> {} : {} ", Utils.toUTC(tx.getUtime()), msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue())); - } - } + Map txs = tonlib.getAllBlockTransactions(fullblock, 100, null); + for (Map.Entry entry : txs.entrySet()) { + for (RawTransaction tx : entry.getValue().getTransactions()) { + if (nonNull(tx.getIn_msg()) + && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { + log.info( + "{} <<<<< {} : {} ", + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue(), 9)); } - - assertThat(rawTransactions.getTransactions().size()).isLessThan(20); - } - - @Test - public void testTonlibGetTxsWithLimitByAddress() { - Address address = Address.of(TON_FOUNDATION); - - log.info("address: " + address.toBounceable()); - - RawTransactions rawTransactions = tonlib.getRawTransactions(address.toRaw(), null, null, 3); - - for (RawTransaction tx : rawTransactions.getTransactions()) { - if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - log.info("{}, {} <<<<< {} : {} ", Utils.toUTC(tx.getUtime()), tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue())); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - log.info("{}, {} >>>>> {} : {} ", Utils.toUTC(tx.getUtime()), msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue())); - } - } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + log.info( + "{} >>>>> {} : {} ", + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue())); + } } - - log.info("total txs: {}", rawTransactions.getTransactions().size()); - assertThat(rawTransactions.getTransactions().size()).isLessThan(4); - } - - - @Test - public void testTonlibGetAllTxsByAddress() { - Address address = Address.of("EQAL66-DGwFvP046ysD_o18wvwt-0A6_aJoVmQpVNIqV_ZvK"); - - log.info("address: " + address.toBounceable()); - - RawTransactions rawTransactions = tonlib.getAllRawTransactions(address.toRaw(), null, null, 51); - - log.info("total txs: {}", rawTransactions.getTransactions().size()); - + } + } + assertThat(txs.size()).isNotEqualTo(0); + } + + @Test + public void testTonlibGetBlockTransactions() { + for (int i = 0; i < 2; i++) { + + MasterChainInfo lastBlock = tonlib.getLast(); + log.info(lastBlock.toString()); + + BlockTransactions blockTransactions = tonlib.getBlockTransactions(lastBlock.getLast(), 100); + log.info(gs.toJson(blockTransactions)); + + for (ShortTxId shortTxId : blockTransactions.getTransactions()) { + Address acccount = Address.of("-1:" + Utils.base64ToHexString(shortTxId.getAccount())); + log.info( + "lt {}, hash {}, account {}", + shortTxId.getLt(), + shortTxId.getHash(), + acccount.toString(false)); + RawTransactions rawTransactions = + tonlib.getRawTransactions( + acccount.toString(false), + BigInteger.valueOf(shortTxId.getLt()), + shortTxId.getHash()); for (RawTransaction tx : rawTransactions.getTransactions()) { - if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - log.info("<<<<< {} - {} : {} ", tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue())); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - log.info(">>>>> {} - {} : {} ", msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue())); - } + if (nonNull(tx.getIn_msg()) + && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { + log.info( + "{}, {} <<<<< {} : {} ", + Utils.toUTC(tx.getUtime()), + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue())); + } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + log.info( + "{}, {} >>>>> {} : {} ", + Utils.toUTC(tx.getUtime()), + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue())); } + } } - - assertThat(rawTransactions.getTransactions().size()).isLessThan(10); - } - - @Test - public void testTonlibGetAllTxsByAddressWithMemo() { - Address address = Address.of("EQCQxq9F4-RSaO-ya7q4CF26yyCaQNY98zgD5ys3ZbbiZdUy"); - - log.info("address: " + address.toBounceable()); - - RawTransactions rawTransactions = tonlib.getAllRawTransactions(address.toRaw(), null, null, 10); - - log.info("total txs: {}", rawTransactions.getTransactions().size()); - - for (RawTransaction tx : rawTransactions.getTransactions()) { - if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - - String msgBodyText; - if (nonNull(tx.getIn_msg().getMsg_data().getBody())) { - - Cell c = CellBuilder.beginCell().fromBoc(Utils.base64ToSignedBytes(tx.getIn_msg().getMsg_data().getBody())).endCell(); - msgBodyText = c.print(); - } else { - msgBodyText = Utils.base64ToString(tx.getIn_msg().getMsg_data().getText()); - } - log.info("<<<<< {} - {} : {}, msgBody cell/text {}, memo {}, memoBytes {}", tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue()), StringUtils.normalizeSpace(msgBodyText), tx.getIn_msg().getMessage(), Utils.bytesToHex(tx.getIn_msg().getMessageBytes())); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - String msgBodyText; - if (nonNull(msg.getMsg_data().getBody())) { - Cell c = CellBuilder.beginCell().fromBoc(Utils.base64ToSignedBytes(msg.getMsg_data().getBody())).endCell(); - msgBodyText = c.print(); - } else { -// msgBodyText = Utils.base64ToString(msg.getMessage()); - msgBodyText = msg.getMessage(); - } - log.info(">>>>> {} - {} : {}, msgBody cell/text {}, memo {}, memoHex {}", msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue()), StringUtils.normalizeSpace(msgBodyText), msg.getMessage(), msg.getMessageHex()); - } - } + } + Utils.sleep(10, "wait for next block"); + } + } + + @Test + public void testTonlibGetTxsByAddress() { + Address address = Address.of(TON_FOUNDATION); + + log.info("address: " + address.toBounceable()); + + RawTransactions rawTransactions = tonlib.getRawTransactions(address.toRaw(), null, null); + + log.info("total txs: {}", rawTransactions.getTransactions().size()); + + for (RawTransaction tx : rawTransactions.getTransactions()) { + if (nonNull(tx.getIn_msg()) + && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { + log.info( + "{}, {} <<<<< {} : {} ", + Utils.toUTC(tx.getUtime()), + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue())); + } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + log.info( + "{}, {} >>>>> {} : {} ", + Utils.toUTC(tx.getUtime()), + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue())); } - - assertThat(rawTransactions.getTransactions().size()).isLessThan(11); - } - - @Test - public void testTonlibGetAllTxsByAddressSmallHistoryLimit() { -// Tonlib tonlib = Tonlib.builder().build(); - - Address address = Address.of(TON_FOUNDATION); - - log.info("address: " + address.toString(true)); - - RawTransactions rawTransactions = tonlib.getAllRawTransactions(address.toRaw(), null, null, 3); - - log.info("total txs: {}", rawTransactions.getTransactions().size()); - - for (RawTransaction tx : rawTransactions.getTransactions()) { - if (nonNull(tx.getIn_msg()) && (StringUtils.isNoneEmpty(tx.getIn_msg().getSource().getAccount_address()))) { - log.info("<<<<< {} - {} : {} ", tx.getIn_msg().getSource().getAccount_address(), tx.getIn_msg().getDestination().getAccount_address(), Utils.formatNanoValue(tx.getIn_msg().getValue())); - } - if (nonNull(tx.getOut_msgs())) { - for (RawMessage msg : tx.getOut_msgs()) { - log.info(">>>>> {} - {} : {} ", msg.getSource().getAccount_address(), msg.getDestination().getAccount_address(), Utils.formatNanoValue(msg.getValue())); - } - } + } + } + + assertThat(rawTransactions.getTransactions().size()).isLessThan(20); + } + + @Test + public void testTonlibGetTxsWithLimitByAddress() { + Address address = Address.of(TON_FOUNDATION); + + log.info("address: " + address.toBounceable()); + + RawTransactions rawTransactions = tonlib.getRawTransactions(address.toRaw(), null, null, 3); + + for (RawTransaction tx : rawTransactions.getTransactions()) { + if (nonNull(tx.getIn_msg()) + && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { + log.info( + "{}, {} <<<<< {} : {} ", + Utils.toUTC(tx.getUtime()), + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue())); + } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + log.info( + "{}, {} >>>>> {} : {} ", + Utils.toUTC(tx.getUtime()), + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue())); } - - assertThat(rawTransactions.getTransactions().size()).isLessThan(4); - } - - - /** - * Create new key pair and sign data using Tonlib library - */ - @Test - public void testTonlibNewKey() { - Key key = tonlib.createNewKey(); - log.info(key.toString()); - String pubKey = Utils.base64UrlSafeToHexString(key.getPublic_key()); - byte[] secKey = Utils.base64ToBytes(key.getSecret()); - - log.info(pubKey); - log.info(Utils.bytesToHex(secKey)); - - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPairFromSeed(secKey); - byte[] secKey2 = keyPair.getSecretKey(); - log.info(Utils.bytesToHex(secKey2)); - assertThat(Utils.bytesToHex(secKey2).contains(Utils.bytesToHex(secKey))).isTrue(); - } - - /** - * Encrypt/Decrypt using key - */ - @Test - public void testTonlibEncryptDecryptKey() { - String secret = "Q3i3Paa45H/F/Is+RW97lxW0eikF0dPClSME6nbogm0="; - String dataToEncrypt = Utils.stringToBase64("ABC"); - Data encrypted = tonlib.encrypt(dataToEncrypt, secret); - log.info("encrypted {}", encrypted.getBytes()); - - Data decrypted = tonlib.decrypt(encrypted.getBytes(), secret); - String dataDecrypted = Utils.base64ToString(decrypted.getBytes()); - log.info("decrypted {}", dataDecrypted); - - assertThat("ABC").isEqualTo(dataDecrypted); - } - - /** - * Encrypt/Decrypt with using mnemonic - */ - @Test - public void testTonlibEncryptDecryptMnemonic() { - String base64mnemonic = Utils.stringToBase64("centring moist twopenny bursary could carbarn abide flirt ground shoelace songster isomeric pis strake jittery penguin gab guileful lierne salivary songbird shore verbal measures"); - String dataToEncrypt = Utils.stringToBase64("ABC"); - Data encrypted = tonlib.encrypt(dataToEncrypt, base64mnemonic); - log.info("encrypted {}", encrypted.getBytes()); - - Data decrypted = tonlib.decrypt(encrypted.getBytes(), base64mnemonic); - String dataDecrypted = Utils.base64ToString(decrypted.getBytes()); - - assertThat("ABC").isEqualTo(dataDecrypted); - } - - @Test - public void testTonlibEncryptDecryptMnemonicModule() throws NoSuchAlgorithmException, InvalidKeyException { - String base64mnemonic = Utils.stringToBase64(Mnemonic.generateString(24)); - - String dataToEncrypt = Utils.stringToBase64("ABC"); - Data encrypted = tonlib.encrypt(dataToEncrypt, base64mnemonic); - log.info("encrypted {}", encrypted.getBytes()); - - Data decrypted = tonlib.decrypt(encrypted.getBytes(), base64mnemonic); - String dataDecrypted = Utils.base64ToString(decrypted.getBytes()); - - assertThat("ABC").isEqualTo(dataDecrypted); + } + } + + log.info("total txs: {}", rawTransactions.getTransactions().size()); + assertThat(rawTransactions.getTransactions().size()).isLessThan(4); + } + + @Test + public void testTonlibGetAllTxsByAddress() { + Address address = Address.of("EQAL66-DGwFvP046ysD_o18wvwt-0A6_aJoVmQpVNIqV_ZvK"); + + log.info("address: " + address.toBounceable()); + + RawTransactions rawTransactions = tonlib.getAllRawTransactions(address.toRaw(), null, null, 51); + + log.info("total txs: {}", rawTransactions.getTransactions().size()); + + for (RawTransaction tx : rawTransactions.getTransactions()) { + if (nonNull(tx.getIn_msg()) + && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { + log.info( + "<<<<< {} - {} : {} ", + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue())); + } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + log.info( + ">>>>> {} - {} : {} ", + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue())); + } + } } - @Test - public void testTonlibRawAccountState() { - Address addr = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); - log.info("address: " + addr.toBounceable()); - - AccountAddressOnly accountAddressOnly = AccountAddressOnly.builder() - .account_address(addr.toBounceable()) - .build(); + assertThat(rawTransactions.getTransactions().size()).isLessThan(10); + } - RawAccountState accountState = tonlib.getRawAccountState(accountAddressOnly); - log.info(accountState.toString()); - log.info("balance: {}", accountState.getBalance()); - assertThat(accountState.getCode()).isNotBlank(); - } + @Test + public void testTonlibGetAllTxsByAddressWithMemo() { + Address address = Address.of("EQCQxq9F4-RSaO-ya7q4CF26yyCaQNY98zgD5ys3ZbbiZdUy"); - @Test - public void testTonlibAccountState() { - Tonlib tonlib = Tonlib.builder() - .pathToGlobalConfig("g:/libs/global-config-archive.json") - .receiveTimeout(5) - .ignoreCache(false) - .build(); - - Address addr = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); - log.info("address: " + addr.toBounceable()); - - AccountAddressOnly accountAddressOnly = AccountAddressOnly.builder() - .account_address(addr.toBounceable()) - .build(); - - FullAccountState accountState = tonlib.getAccountState(accountAddressOnly); - log.info(accountState.toString()); - log.info("balance: {}", accountState.getBalance()); - assertThat(accountState.getLast_transaction_id().getHash()).isNotBlank(); - log.info("last {}", tonlib.getLast()); - } + log.info("address: " + address.toBounceable()); - @Test - public void testTonlibAccountStateAtSeqno() { - Tonlib tonlib = Tonlib.builder() - .pathToGlobalConfig("g:/libs/global-config-archive.json") - .receiveTimeout(5) - .ignoreCache(false) - .build(); - - Address addr = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); - log.info("address: " + addr.toBounceable()); - - BlockIdExt blockId = tonlib.lookupBlock(39047069, -1, -9223372036854775808L, 0, 0); - FullAccountState accountState = tonlib.getAccountState(addr, blockId); - log.info(accountState.toString()); - log.info("balance: {}", accountState.getBalance()); - assertThat(accountState.getLast_transaction_id().getHash()).isNotBlank(); - log.info("last {}", tonlib.getLast()); - } + RawTransactions rawTransactions = tonlib.getAllRawTransactions(address.toRaw(), null, null, 10); - @Test - public void testTonlibKeystorePath() { - Tonlib tonlib = Tonlib.builder() - .keystoreInMemory(false) - .keystorePath("D:/") - .verbosityLevel(VerbosityLevel.INFO) - .build(); - Address address = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); - RunResult result = tonlib.runMethod(address, "seqno"); - log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); - TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); - log.info("seqno: {}", seqno.getNumber()); - assertThat(result.getExit_code()).isZero(); - } + log.info("total txs: {}", rawTransactions.getTransactions().size()); - @Test - public void testTonlibRunMethodSeqno() { - Address address = Address.of(TON_FOUNDATION); - RunResult result = tonlib.runMethod(address, "seqno"); - log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); - TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); - log.info("seqno: {}", seqno.getNumber()); - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibRunMethodSeqnoAtBlockId() { - Tonlib tonlib = Tonlib.builder() - .pathToGlobalConfig("g:/libs/global-config-archive.json") - .receiveTimeout(5) - .ignoreCache(false) - .build(); - Address address = Address.of(TON_FOUNDATION); - RunResult result = tonlib.runMethod(address, "seqno", 39047069); - log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); - TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); - log.info("seqno: {}", seqno.getNumber()); - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibRunMethodGetJetton() { - Address address = Address.of("EQBYzFXx0QTPW5Lo63ArbNasI_GWRj7NwcAcJR2IWo7_3nTp"); - RunResult result = tonlib.runMethod(address, "get_jetton_data"); - log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); - log.info("result: {}", result); - assertThat(result.getExit_code()).isZero(); - } + for (RawTransaction tx : rawTransactions.getTransactions()) { + if (nonNull(tx.getIn_msg()) + && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { - @Test - public void testTonlibRunMethodParticipantsList() { - Address address = Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); - - RunResult result = tonlib.runMethod(address, "participant_list"); - log.info(result.toString()); - TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); - for (Object o : listResult.getList().getElements()) { - TvmStackEntryTuple t = (TvmStackEntryTuple) o; - TvmTuple tuple = t.getTuple(); - TvmStackEntryNumber addr = (TvmStackEntryNumber) tuple.getElements().get(0); - TvmStackEntryNumber stake = (TvmStackEntryNumber) tuple.getElements().get(1); - log.info("{}, {}", addr.getNumber(), stake.getNumber()); - } - assertThat(result.getExit_code()).isZero(); - } + String msgBodyText; + if (nonNull(tx.getIn_msg().getMsg_data().getBody())) { - @Test - public void testTonlibRunMethodParticipantsListInThePast() { - Tonlib tonlib = Tonlib.builder() - .pathToGlobalConfig("g:/libs/global-config-archive.json") - .receiveTimeout(5) - .ignoreCache(false) - .build(); - Address address = Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); - - RunResult result = tonlib.runMethod(address, "participant_list", 39047069); - log.info(result.toString()); - TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); - for (Object o : listResult.getList().getElements()) { - TvmStackEntryTuple t = (TvmStackEntryTuple) o; - TvmTuple tuple = t.getTuple(); - TvmStackEntryNumber addr = (TvmStackEntryNumber) tuple.getElements().get(0); - TvmStackEntryNumber stake = (TvmStackEntryNumber) tuple.getElements().get(1); - log.info("{}, {}", addr.getNumber(), stake.getNumber()); + Cell c = + CellBuilder.beginCell() + .fromBoc(Utils.base64ToSignedBytes(tx.getIn_msg().getMsg_data().getBody())) + .endCell(); + msgBodyText = c.print(); + } else { + msgBodyText = Utils.base64ToString(tx.getIn_msg().getMsg_data().getText()); } - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibRunMethodActiveElectionId() { - Address address = Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); - RunResult result = tonlib.runMethod(address, "active_election_id"); - TvmStackEntryNumber electionId = (TvmStackEntryNumber) result.getStack().get(0); - log.info("electionId: {}", electionId.getNumber()); - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibRunMethodActiveElectionIdAtSeqno() { - Tonlib tonlib = Tonlib.builder() - .pathToGlobalConfig("g:/libs/global-config-archive.json") - .receiveTimeout(5) - .ignoreCache(false) - .build(); - Address address = Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); - RunResult result = tonlib.runMethod(address, "active_election_id", 39047069); - TvmStackEntryNumber electionId = (TvmStackEntryNumber) result.getStack().get(0); - log.info("electionId: {}", electionId.getNumber()); - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibRunMethodPastElectionsId() { - Address address = Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); - RunResult result = tonlib.runMethod(address, "past_election_ids"); - TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); - for (Object o : listResult.getList().getElements()) { - TvmStackEntryNumber electionId = (TvmStackEntryNumber) o; - log.info(electionId.getNumber().toString()); + log.info( + "<<<<< {} - {} : {}, msgBody cell/text {}, memo {}, memoBytes {}", + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue()), + StringUtils.normalizeSpace(msgBodyText), + tx.getIn_msg().getMessage(), + Utils.bytesToHex(tx.getIn_msg().getMessageBytes())); + } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + String msgBodyText; + if (nonNull(msg.getMsg_data().getBody())) { + Cell c = + CellBuilder.beginCell() + .fromBoc(Utils.base64ToSignedBytes(msg.getMsg_data().getBody())) + .endCell(); + msgBodyText = c.print(); + } else { + // msgBodyText = Utils.base64ToString(msg.getMessage()); + msgBodyText = msg.getMessage(); + } + log.info( + ">>>>> {} - {} : {}, msgBody cell/text {}, memo {}, memoHex {}", + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue()), + StringUtils.normalizeSpace(msgBodyText), + msg.getMessage(), + msg.getMessageHex()); } - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibRunMethodPastElections() { - Address address = Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); - RunResult result = tonlib.runMethod(address, "past_elections"); - TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); - log.info("pastElections: {}", listResult); - - assertThat(result.getExit_code()).isZero(); - } - - @Test - public void testTonlibGetConfig() { - Tonlib tonlib = Tonlib - .builder() - .build(); - MasterChainInfo mc = tonlib.getLast(); - Cell c = tonlib.getConfigParam(mc.getLast(), 22); - log.info(c.print()); - } - - @Test - public void testTonlibGetConfigAll() { - Cell c = tonlib.getConfigAll(128); - log.info(c.print()); - } - - @Test - public void testTonlibLoadContract() { - AccountAddressOnly address = AccountAddressOnly.builder().account_address("EQAPZ3Trml6zO403fnA6fiqbjPw9JcOCSk0OVY6dVdyM2fEM").build(); - long result = tonlib.loadContract(address); - log.info("result {}", result); - } - - @Test - public void testTonlibLoadContractSeqno() { - AccountAddressOnly address = AccountAddressOnly.builder().account_address("EQAPZ3Trml6zO403fnA6fiqbjPw9JcOCSk0OVY6dVdyM2fEM").build(); - long result = tonlib.loadContract(address, 36661567); - log.info("result {}", result); - } - - @Test - public void testTonlibRunMethodComputeReturnedStake() { - Address elector = Address.of(ELECTOR_ADDRESSS); - RunResult result = tonlib.runMethod(elector, "compute_returned_stake", new ArrayDeque<>()); - log.info("result: {}", result); - assertThat(result.getExit_code()).isEqualTo(2); // error since compute_returned_stake requires an argument - - Deque stack = new ArrayDeque<>(); - Address validatorAddress = Address.of("Ef_sR2c8U-tNfCU5klvd60I5VMXUd_U9-22uERrxrrt3uzYi"); - stack.offer("[num," + validatorAddress.toDecimal() + "]"); - - result = tonlib.runMethod(elector, "compute_returned_stake", stack); - BigInteger returnStake = ((TvmStackEntryNumber) result.getStack().get(0)).getNumber(); - log.info("return stake: {} ", Utils.formatNanoValue(returnStake.longValue())); + } } - @Test - @Ignore - public void testTonlibMyLocalTon() { - Tonlib tonlib = Tonlib.builder() - .verbosityLevel(VerbosityLevel.DEBUG) - .pathToGlobalConfig("G:/Git_Projects/MyLocalTon/myLocalTon/genesis/db/my-ton-global.config.json") - .ignoreCache(true) - .build(); - - BlockIdExt blockIdExt = tonlib.getMasterChainInfo().getLast(); - Cell cellConfig8 = tonlib.getConfigParam(blockIdExt, 8); - ConfigParams8 config8 = ConfigParams8.deserialize(CellSlice.beginParse(cellConfig8)); - log.info("config 8: {}", config8); - RunResult seqno = tonlib.runMethod(Address.of("-1:CF624357217E2C9D2F4F5CA65F82FCBD16949FA00F46CA51358607BEF6D2CB53"), "seqno"); - log.info("seqno RunResult {}", seqno); - FullAccountState accountState1 = tonlib.getAccountState(Address.of("-1:85cda44e9838bf5a8c6d1de95c3e22b92884ae70ee1b550723a92a8ca0df3321")); - RawAccountState accountState2 = tonlib.getRawAccountState(Address.of("-1:85cda44e9838bf5a8c6d1de95c3e22b92884ae70ee1b550723a92a8ca0df3321")); - - log.info("full accountState {}", accountState1); - log.info("raw accountState {}", accountState2); - } - - @Test - public void testTonlibLookupBlock() { - MasterChainInfo mcInfo = tonlib.getLast(); - - Shards shards = tonlib.getShards(mcInfo.getLast().getSeqno(), 0, 0); - log.info("shards-- {}", shards.getShards()); - - BlockIdExt shard = shards.getShards().get(0); - - BlockIdExt fullblock = tonlib.lookupBlock(shard.getSeqno(), shard.getWorkchain(), shard.getShard(), 0, 0); - log.info("fullBlock-- {}", fullblock); - assertThat(fullblock).isNotNull(); - } + assertThat(rawTransactions.getTransactions().size()).isLessThan(11); + } - @Test - public void testTonlibTryLocateTxByIncomingMessage() { - RawTransaction tx = tonlib.tryLocateTxByIncomingMessage( - Address.of("EQAuMjwyuQBaaxM6ooRJWbuUacQvBgVEWQOSSlbMERG0ljRD"), - Address.of("EQDEruSI2frAF-GdzpjDLWWBKnwREDAJmu7eIEFG6zdUlXVE"), - 26521292000002L); + @Test + public void testTonlibGetAllTxsByAddressSmallHistoryLimit() { + // Tonlib tonlib = Tonlib.builder().build(); - log.info("found tx {}", tx); - - assertThat(tx.getIn_msg()).isNotNull(); - } - - @Test - public void testTonlibTryLocateTxByOutcomingMessage() { - RawTransaction tx = tonlib.tryLocateTxByOutcomingMessage( - Address.of("EQAuMjwyuQBaaxM6ooRJWbuUacQvBgVEWQOSSlbMERG0ljRD"), - Address.of("EQDEruSI2frAF-GdzpjDLWWBKnwREDAJmu7eIEFG6zdUlXVE"), - 26521292000002L); - - log.info("found tx {}", tx); - - assertThat(tx.getIn_msg()).isNotNull(); - assertThat(tx.getOut_msgs()).isNotNull(); - } + Address address = Address.of(TON_FOUNDATION); + log.info("address: " + address.toString(true)); - @Test - public void testTonlibStateAndStatus() { + RawTransactions rawTransactions = tonlib.getAllRawTransactions(address.toRaw(), null, null, 3); - FullAccountState accountState1 = tonlib.getAccountState(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq")); - log.info("FullAccountState {}", accountState1); + log.info("total txs: {}", rawTransactions.getTransactions().size()); - RawAccountState accountState2 = tonlib.getRawAccountState(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq")); - log.info("RawAccountState {}", accountState2); - - BlockIdExt blockId = BlockIdExt.builder() - .workchain(-1) - .shard(-9223372036854775808L) - .seqno(27894542) - .root_hash("akLZC86Ve0IPDW2HGhSCKKz7RkJk1yAgl34qMpo1RlE=") - .file_hash("KjJvOENNPc39inKO2cOce0s/fUX9Nv8/qdS1VFj2yw0=") - .build(); - - log.info("input blockId {}", blockId); - - FullAccountState accountState1AtBlock = tonlib.getAccountState(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq"), blockId); - log.info("accountState1AtBlock {}", accountState1AtBlock); - - RawAccountState accountState2AtBlock = tonlib.getRawAccountState(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq"), blockId); - log.info("RawAccountStateAtBlock {}", accountState2AtBlock); - - String accountState1Status = tonlib.getRawAccountStatus(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq")); - log.info("=========================================="); - - log.info("wallet_id {}, seqno {}", accountState1.getAccount_state().getWallet_id(), accountState1.getAccount_state().getSeqno()); - log.info("frozen_hash {}, status {}", accountState1.getAccount_state().getFrozen_hash(), accountState1Status); - log.info("rawAccountState2 {}", accountState2); - assertThat(accountState1.getBalance()).isEqualTo(accountState2.getBalance()); - } - - @Test - public void testTonlibGetLibraries() { - SmcLibraryResult result = tonlib.getLibraries( - Collections.singletonList("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M=")); - log.info("result: {}", result); - - assertThat(result.getResult().get(0).getHash()).isEqualTo("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M="); - } -} \ No newline at end of file + for (RawTransaction tx : rawTransactions.getTransactions()) { + if (nonNull(tx.getIn_msg()) + && (StringUtils.isNoneEmpty(tx.getIn_msg().getSource().getAccount_address()))) { + log.info( + "<<<<< {} - {} : {} ", + tx.getIn_msg().getSource().getAccount_address(), + tx.getIn_msg().getDestination().getAccount_address(), + Utils.formatNanoValue(tx.getIn_msg().getValue())); + } + if (nonNull(tx.getOut_msgs())) { + for (RawMessage msg : tx.getOut_msgs()) { + log.info( + ">>>>> {} - {} : {} ", + msg.getSource().getAccount_address(), + msg.getDestination().getAccount_address(), + Utils.formatNanoValue(msg.getValue())); + } + } + } + + assertThat(rawTransactions.getTransactions().size()).isLessThan(4); + } + + /** Create new key pair and sign data using Tonlib library */ + @Test + public void testTonlibNewKey() { + Key key = tonlib.createNewKey(); + log.info(key.toString()); + String pubKey = Utils.base64UrlSafeToHexString(key.getPublic_key()); + byte[] secKey = Utils.base64ToBytes(key.getSecret()); + + log.info(pubKey); + log.info(Utils.bytesToHex(secKey)); + + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPairFromSeed(secKey); + byte[] secKey2 = keyPair.getSecretKey(); + log.info(Utils.bytesToHex(secKey2)); + assertThat(Utils.bytesToHex(secKey2).contains(Utils.bytesToHex(secKey))).isTrue(); + } + + /** Encrypt/Decrypt using key */ + @Test + public void testTonlibEncryptDecryptKey() { + String secret = "Q3i3Paa45H/F/Is+RW97lxW0eikF0dPClSME6nbogm0="; + String dataToEncrypt = Utils.stringToBase64("ABC"); + Data encrypted = tonlib.encrypt(dataToEncrypt, secret); + log.info("encrypted {}", encrypted.getBytes()); + + Data decrypted = tonlib.decrypt(encrypted.getBytes(), secret); + String dataDecrypted = Utils.base64ToString(decrypted.getBytes()); + log.info("decrypted {}", dataDecrypted); + + assertThat("ABC").isEqualTo(dataDecrypted); + } + + /** Encrypt/Decrypt with using mnemonic */ + @Test + public void testTonlibEncryptDecryptMnemonic() { + String base64mnemonic = + Utils.stringToBase64( + "centring moist twopenny bursary could carbarn abide flirt ground shoelace songster isomeric pis strake jittery penguin gab guileful lierne salivary songbird shore verbal measures"); + String dataToEncrypt = Utils.stringToBase64("ABC"); + Data encrypted = tonlib.encrypt(dataToEncrypt, base64mnemonic); + log.info("encrypted {}", encrypted.getBytes()); + + Data decrypted = tonlib.decrypt(encrypted.getBytes(), base64mnemonic); + String dataDecrypted = Utils.base64ToString(decrypted.getBytes()); + + assertThat("ABC").isEqualTo(dataDecrypted); + } + + @Test + public void testTonlibEncryptDecryptMnemonicModule() + throws NoSuchAlgorithmException, InvalidKeyException { + String base64mnemonic = Utils.stringToBase64(Mnemonic.generateString(24)); + + String dataToEncrypt = Utils.stringToBase64("ABC"); + Data encrypted = tonlib.encrypt(dataToEncrypt, base64mnemonic); + log.info("encrypted {}", encrypted.getBytes()); + + Data decrypted = tonlib.decrypt(encrypted.getBytes(), base64mnemonic); + String dataDecrypted = Utils.base64ToString(decrypted.getBytes()); + + assertThat("ABC").isEqualTo(dataDecrypted); + } + + @Test + public void testTonlibRawAccountState() { + Address addr = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); + log.info("address: " + addr.toBounceable()); + + AccountAddressOnly accountAddressOnly = + AccountAddressOnly.builder().account_address(addr.toBounceable()).build(); + + RawAccountState accountState = tonlib.getRawAccountState(accountAddressOnly); + log.info(accountState.toString()); + log.info("balance: {}", accountState.getBalance()); + assertThat(accountState.getCode()).isNotBlank(); + } + + @Test + public void testTonlibAccountState() { + Tonlib tonlib = + Tonlib.builder() + .pathToGlobalConfig("g:/libs/global-config-archive.json") + .receiveTimeout(5) + .ignoreCache(false) + .build(); + + Address addr = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); + log.info("address: " + addr.toBounceable()); + + AccountAddressOnly accountAddressOnly = + AccountAddressOnly.builder().account_address(addr.toBounceable()).build(); + + FullAccountState accountState = tonlib.getAccountState(accountAddressOnly); + log.info(accountState.toString()); + log.info("balance: {}", accountState.getBalance()); + assertThat(accountState.getLast_transaction_id().getHash()).isNotBlank(); + log.info("last {}", tonlib.getLast()); + } + + @Test + public void testTonlibAccountStateAtSeqno() { + Tonlib tonlib = + Tonlib.builder() + .pathToGlobalConfig("g:/libs/global-config-archive.json") + .receiveTimeout(5) + .ignoreCache(false) + .build(); + + Address addr = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); + log.info("address: " + addr.toBounceable()); + + BlockIdExt blockId = tonlib.lookupBlock(39047069, -1, -9223372036854775808L, 0, 0); + FullAccountState accountState = tonlib.getAccountState(addr, blockId); + log.info(accountState.toString()); + log.info("balance: {}", accountState.getBalance()); + assertThat(accountState.getLast_transaction_id().getHash()).isNotBlank(); + log.info("last {}", tonlib.getLast()); + } + + @Test + public void testTonlibKeystorePath() { + Tonlib tonlib = + Tonlib.builder() + .keystoreInMemory(false) + .keystorePath("D:/") + .verbosityLevel(VerbosityLevel.INFO) + .build(); + Address address = Address.of("Ef8-sf_0CQDgwW6kNuNY8mUvRW-MGQ34Evffj8O0Z9Ly1tZ4"); + RunResult result = tonlib.runMethod(address, "seqno"); + log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); + TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); + log.info("seqno: {}", seqno.getNumber()); + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodSeqno() { + Address address = Address.of(TON_FOUNDATION); + RunResult result = tonlib.runMethod(address, "seqno"); + log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); + TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); + log.info("seqno: {}", seqno.getNumber()); + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodSeqnoAtBlockId() { + Tonlib tonlib = + Tonlib.builder() + .pathToGlobalConfig("g:/libs/global-config-archive.json") + .receiveTimeout(5) + .ignoreCache(false) + .build(); + Address address = Address.of(TON_FOUNDATION); + RunResult result = tonlib.runMethod(address, "seqno", 39047069); + log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); + TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); + log.info("seqno: {}", seqno.getNumber()); + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodGetJetton() { + Address address = Address.of("EQBYzFXx0QTPW5Lo63ArbNasI_GWRj7NwcAcJR2IWo7_3nTp"); + RunResult result = tonlib.runMethod(address, "get_jetton_data"); + log.info("gas_used {}, exit_code {} ", result.getGas_used(), result.getExit_code()); + log.info("result: {}", result); + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodParticipantsList() { + Address address = + Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); + + RunResult result = tonlib.runMethod(address, "participant_list"); + log.info(result.toString()); + TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); + for (Object o : listResult.getList().getElements()) { + TvmStackEntryTuple t = (TvmStackEntryTuple) o; + TvmTuple tuple = t.getTuple(); + TvmStackEntryNumber addr = (TvmStackEntryNumber) tuple.getElements().get(0); + TvmStackEntryNumber stake = (TvmStackEntryNumber) tuple.getElements().get(1); + log.info("{}, {}", addr.getNumber(), stake.getNumber()); + } + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodParticipantsListInThePast() { + Tonlib tonlib = + Tonlib.builder() + .pathToGlobalConfig("g:/libs/global-config-archive.json") + .receiveTimeout(5) + .ignoreCache(false) + .build(); + Address address = + Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); + + RunResult result = tonlib.runMethod(address, "participant_list", 39047069); + log.info(result.toString()); + TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); + for (Object o : listResult.getList().getElements()) { + TvmStackEntryTuple t = (TvmStackEntryTuple) o; + TvmTuple tuple = t.getTuple(); + TvmStackEntryNumber addr = (TvmStackEntryNumber) tuple.getElements().get(0); + TvmStackEntryNumber stake = (TvmStackEntryNumber) tuple.getElements().get(1); + log.info("{}, {}", addr.getNumber(), stake.getNumber()); + } + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodActiveElectionId() { + Address address = + Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); + RunResult result = tonlib.runMethod(address, "active_election_id"); + TvmStackEntryNumber electionId = (TvmStackEntryNumber) result.getStack().get(0); + log.info("electionId: {}", electionId.getNumber()); + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodActiveElectionIdAtSeqno() { + Tonlib tonlib = + Tonlib.builder() + .pathToGlobalConfig("g:/libs/global-config-archive.json") + .receiveTimeout(5) + .ignoreCache(false) + .build(); + Address address = + Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); + RunResult result = tonlib.runMethod(address, "active_election_id", 39047069); + TvmStackEntryNumber electionId = (TvmStackEntryNumber) result.getStack().get(0); + log.info("electionId: {}", electionId.getNumber()); + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodPastElectionsId() { + Address address = + Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); + RunResult result = tonlib.runMethod(address, "past_election_ids"); + TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); + for (Object o : listResult.getList().getElements()) { + TvmStackEntryNumber electionId = (TvmStackEntryNumber) o; + log.info(electionId.getNumber().toString()); + } + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibRunMethodPastElections() { + Address address = + Address.of("-1:3333333333333333333333333333333333333333333333333333333333333333"); + RunResult result = tonlib.runMethod(address, "past_elections"); + TvmStackEntryList listResult = (TvmStackEntryList) result.getStack().get(0); + log.info("pastElections: {}", listResult); + + assertThat(result.getExit_code()).isZero(); + } + + @Test + public void testTonlibGetConfig() { + Tonlib tonlib = Tonlib.builder().build(); + MasterChainInfo mc = tonlib.getLast(); + Cell c = tonlib.getConfigParam(mc.getLast(), 22); + log.info(c.print()); + } + + @Test + public void testTonlibGetConfigAll() { + Cell c = tonlib.getConfigAll(128); + log.info(c.print()); + } + + @Test + public void testTonlibLoadContract() { + AccountAddressOnly address = + AccountAddressOnly.builder() + .account_address("EQAPZ3Trml6zO403fnA6fiqbjPw9JcOCSk0OVY6dVdyM2fEM") + .build(); + long result = tonlib.loadContract(address); + log.info("result {}", result); + } + + @Test + public void testTonlibLoadContractSeqno() { + AccountAddressOnly address = + AccountAddressOnly.builder() + .account_address("EQAPZ3Trml6zO403fnA6fiqbjPw9JcOCSk0OVY6dVdyM2fEM") + .build(); + long result = tonlib.loadContract(address, 36661567); + log.info("result {}", result); + } + + @Test + public void testTonlibRunMethodComputeReturnedStake() { + Address elector = Address.of(ELECTOR_ADDRESSS); + RunResult result = tonlib.runMethod(elector, "compute_returned_stake", new ArrayDeque<>()); + log.info("result: {}", result); + assertThat(result.getExit_code()) + .isEqualTo(2); // error since compute_returned_stake requires an argument + + Deque stack = new ArrayDeque<>(); + Address validatorAddress = Address.of("Ef_sR2c8U-tNfCU5klvd60I5VMXUd_U9-22uERrxrrt3uzYi"); + stack.offer("[num," + validatorAddress.toDecimal() + "]"); + + result = tonlib.runMethod(elector, "compute_returned_stake", stack); + BigInteger returnStake = ((TvmStackEntryNumber) result.getStack().get(0)).getNumber(); + log.info("return stake: {} ", Utils.formatNanoValue(returnStake.longValue())); + } + + @Test + @Ignore + public void testTonlibMyLocalTon() { + Tonlib tonlib = + Tonlib.builder() + .verbosityLevel(VerbosityLevel.DEBUG) + .pathToGlobalConfig( + "G:/Git_Projects/MyLocalTon/myLocalTon/genesis/db/my-ton-global.config.json") + .ignoreCache(true) + .build(); + + BlockIdExt blockIdExt = tonlib.getMasterChainInfo().getLast(); + Cell cellConfig8 = tonlib.getConfigParam(blockIdExt, 8); + ConfigParams8 config8 = ConfigParams8.deserialize(CellSlice.beginParse(cellConfig8)); + log.info("config 8: {}", config8); + RunResult seqno = + tonlib.runMethod( + Address.of("-1:CF624357217E2C9D2F4F5CA65F82FCBD16949FA00F46CA51358607BEF6D2CB53"), + "seqno"); + log.info("seqno RunResult {}", seqno); + FullAccountState accountState1 = + tonlib.getAccountState( + Address.of("-1:85cda44e9838bf5a8c6d1de95c3e22b92884ae70ee1b550723a92a8ca0df3321")); + RawAccountState accountState2 = + tonlib.getRawAccountState( + Address.of("-1:85cda44e9838bf5a8c6d1de95c3e22b92884ae70ee1b550723a92a8ca0df3321")); + + log.info("full accountState {}", accountState1); + log.info("raw accountState {}", accountState2); + } + + @Test + public void testTonlibLookupBlock() { + MasterChainInfo mcInfo = tonlib.getLast(); + + Shards shards = tonlib.getShards(mcInfo.getLast().getSeqno(), 0, 0); + log.info("shards-- {}", shards.getShards()); + + BlockIdExt shard = shards.getShards().get(0); + + BlockIdExt fullblock = + tonlib.lookupBlock(shard.getSeqno(), shard.getWorkchain(), shard.getShard(), 0, 0); + log.info("fullBlock-- {}", fullblock); + assertThat(fullblock).isNotNull(); + } + + @Test + public void testTonlibTryLocateTxByIncomingMessage() { + RawTransaction tx = + tonlib.tryLocateTxByIncomingMessage( + Address.of("EQAuMjwyuQBaaxM6ooRJWbuUacQvBgVEWQOSSlbMERG0ljRD"), + Address.of("EQDEruSI2frAF-GdzpjDLWWBKnwREDAJmu7eIEFG6zdUlXVE"), + 26521292000002L); + + log.info("found tx {}", tx); + + assertThat(tx.getIn_msg()).isNotNull(); + } + + @Test + public void testTonlibTryLocateTxByOutcomingMessage() { + RawTransaction tx = + tonlib.tryLocateTxByOutcomingMessage( + Address.of("EQAuMjwyuQBaaxM6ooRJWbuUacQvBgVEWQOSSlbMERG0ljRD"), + Address.of("EQDEruSI2frAF-GdzpjDLWWBKnwREDAJmu7eIEFG6zdUlXVE"), + 26521292000002L); + + log.info("found tx {}", tx); + + assertThat(tx.getIn_msg()).isNotNull(); + assertThat(tx.getOut_msgs()).isNotNull(); + } + + @Test + public void testTonlibStateAndStatus() { + + FullAccountState accountState1 = + tonlib.getAccountState(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq")); + log.info("FullAccountState {}", accountState1); + + RawAccountState accountState2 = + tonlib.getRawAccountState(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq")); + log.info("RawAccountState {}", accountState2); + + BlockIdExt blockId = + BlockIdExt.builder() + .workchain(-1) + .shard(-9223372036854775808L) + .seqno(27894542) + .root_hash("akLZC86Ve0IPDW2HGhSCKKz7RkJk1yAgl34qMpo1RlE=") + .file_hash("KjJvOENNPc39inKO2cOce0s/fUX9Nv8/qdS1VFj2yw0=") + .build(); + + log.info("input blockId {}", blockId); + + FullAccountState accountState1AtBlock = + tonlib.getAccountState( + Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq"), blockId); + log.info("accountState1AtBlock {}", accountState1AtBlock); + + RawAccountState accountState2AtBlock = + tonlib.getRawAccountState( + Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq"), blockId); + log.info("RawAccountStateAtBlock {}", accountState2AtBlock); + + String accountState1Status = + tonlib.getRawAccountStatus(Address.of("EQCtPHFrtkIw3UC2rNfSgVWYT1MiMLDUtgMy2M7j1P_eNMDq")); + log.info("=========================================="); + + log.info( + "wallet_id {}, seqno {}", + accountState1.getAccount_state().getWallet_id(), + accountState1.getAccount_state().getSeqno()); + log.info( + "frozen_hash {}, status {}", + accountState1.getAccount_state().getFrozen_hash(), + accountState1Status); + log.info("rawAccountState2 {}", accountState2); + assertThat(accountState1.getBalance()).isEqualTo(accountState2.getBalance()); + } + + @Test + public void testTonlibGetLibraries() { + SmcLibraryResult result = + tonlib.getLibraries( + Collections.singletonList("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M=")); + log.info("result: {}", result); + + assertThat(result.getResult().get(0).getHash()) + .isEqualTo("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M="); + } +}