From 351f37ea62e49441d7c2ce1850e8997b34123a85 Mon Sep 17 00:00:00 2001 From: neodiX Date: Tue, 19 Nov 2024 17:34:44 +0400 Subject: [PATCH] added initial balance to all transactions summary; adjusted print msg and tx tables (added block and comment columns); added lite-client to Blockchain; add parse shard into LiteClientParser; add print txs and msgs to lite-client results; --- .../java/org/ton/java/address/Address.java | 524 +-- blockchain/pom.xml | 5 + .../main/java/org/ton/java/Blockchain.java | 1686 +++++---- .../java/org/ton/java/BlockchainTest.java | 888 +++-- .../src/main/java/org/ton/java/cell/Cell.java | 1831 +++++----- .../java/org/ton/java/cell/CellBuilder.java | 18 +- .../java/org/ton/java/tlb/types/Block.java | 217 +- .../ton/java/tlb/types/ComputePhaseVM.java | 17 +- .../org/ton/java/tlb/types/MessageFees.java | 86 - .../ton/java/tlb/types/MessagePrintInfo.java | 88 + .../org/ton/java/tlb/types/StoragePhase.java | 3 +- .../org/ton/java/tlb/types/Transaction.java | 1271 ++++--- .../tlb/types/TransactionDescription.java | 136 +- .../ton/java/tlb/types/TransactionFees.java | 26 - .../java/tlb/types/TransactionPrintInfo.java | 41 + .../java/tlb/TestTlbTransactionReader.java | 20 + .../org/ton/java/emulator/TestTxEmulator.java | 1415 ++++---- liteclient/pom.xml | 6 + .../org/ton/java/liteclient/LiteClient.java | 1095 +++--- .../ton/java/liteclient/LiteClientParser.java | 3141 +++++++++-------- .../java/liteclient/api/ResultLastBlock.java | 7 +- .../liteclient/api/block/AccountBlock.java | 24 +- .../ton/java/liteclient/api/block/Block.java | 51 +- .../ton/java/liteclient/api/block/Info.java | 29 +- .../api/block/LiteClientAddress.java | 14 +- .../liteclient/api/block/MessageFees.java | 88 + .../liteclient/api/block/Transaction.java | 373 +- .../liteclient/api/block/TransactionFees.java | 30 + .../main/resources/testnet-global.config.json | 130 +- .../ton/java/liteclient/LiteClientTest.java | 711 ++-- liteclient/src/test/resources/logback.xml | 14 + .../smartcontract/SmartContractCompiler.java | 214 +- .../java/smartcontract/wallet/Contract.java | 331 +- .../main/java/org/ton/java/tl/types/Text.java | 2 +- .../main/java/org/ton/java/tonlib/Tonlib.java | 1093 +++--- .../org/ton/java/tonlib/types/BlockIdExt.java | 11 + .../org/ton/java/tonlib/types/RawMessage.java | 18 +- .../java/tonlib/types/RawTransactions.java | 8 +- .../org/ton/java/tonlib/TestTonlibJson.java | 59 + .../main/java/org/ton/java/utils/Utils.java | 2303 ++++++------ 40 files changed, 9724 insertions(+), 8300 deletions(-) delete mode 100644 cell/src/main/java/org/ton/java/tlb/types/MessageFees.java create mode 100644 cell/src/main/java/org/ton/java/tlb/types/MessagePrintInfo.java delete mode 100644 cell/src/main/java/org/ton/java/tlb/types/TransactionFees.java create mode 100644 cell/src/main/java/org/ton/java/tlb/types/TransactionPrintInfo.java create mode 100644 liteclient/src/main/java/org/ton/java/liteclient/api/block/MessageFees.java create mode 100644 liteclient/src/main/java/org/ton/java/liteclient/api/block/TransactionFees.java create mode 100644 liteclient/src/test/resources/logback.xml diff --git a/address/src/main/java/org/ton/java/address/Address.java b/address/src/main/java/org/ton/java/address/Address.java index de3668c3..e949389f 100644 --- a/address/src/main/java/org/ton/java/address/Address.java +++ b/address/src/main/java/org/ton/java/address/Address.java @@ -1,6 +1,6 @@ package org.ton.java.address; -import static java.util.Objects.isNull; +import org.ton.java.utils.Utils; import java.io.IOException; import java.math.BigInteger; @@ -8,301 +8,319 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; -import org.ton.java.utils.Utils; - -public class Address { - private static final byte bounceable_tag = 0x11; - private static final byte non_bounceable_tag = 0x51; - private static final int test_flag = 0x80; +import static java.util.Objects.isNull; - public byte wc; - public byte[] hashPart; - public boolean isTestOnly; - public boolean isUserFriendly; - public boolean isBounceable; +public class Address { - /** - * More details bounceable-vs-non-bounceable-addresses - */ - public boolean isWallet; + private static final byte bounceable_tag = 0x11; + private static final byte non_bounceable_tag = 0x51; + private static final int test_flag = 0x80; - public boolean isUrlSafe; + public byte wc; + public byte[] hashPart; + public boolean isTestOnly; + public boolean isUserFriendly; + public boolean isBounceable; - public AddressType addressType; // todo + /** + * More details bounceable-vs-non-bounceable-addresses + */ + public boolean isWallet; - private Address() {} + public boolean isUrlSafe; - public Address(String address) { + public AddressType addressType; // todo - if (isNull(address)) { - throw new IllegalArgumentException("Address is null"); + private Address() { } - if (address.indexOf(':') == -1) { - if (address.indexOf('-') != -1 || address.indexOf('_') != -1) { - isUrlSafe = true; - // convert to unsafe URL - address = address.replace('-', '+').replace('_', '/'); - } else { - isUrlSafe = false; - } + public Address(String address) { + + if (isNull(address)) { + throw new IllegalArgumentException("Address is null"); + } + + if (address.indexOf(':') == -1) { + if (address.indexOf('-') != -1 || address.indexOf('_') != -1) { + isUrlSafe = true; + // convert to unsafe URL + address = address.replace('-', '+').replace('_', '/'); + } else { + isUrlSafe = false; + } + } + + int colonIndex = address.indexOf(':'); + if (colonIndex != -1) { + + if (colonIndex != address.lastIndexOf(':')) { + throw new Error("Invalid address " + address); + } + + String wcPart = address.substring(0, colonIndex); + String hexPart = address.substring(colonIndex + 1); + + byte wcInternal = Byte.parseByte(wcPart); + + if (wcInternal != 0 && wcInternal != -1) { + throw new Error("Invalid address wc " + address); + } + + if (hexPart.length() == 63) { + hexPart = "0" + hexPart; + } + if (hexPart.length() == 1) { + hexPart = "000000000000000000000000000000000000000000000000000000000000000" + hexPart; + } + + if (hexPart.length() != 64) { + throw new Error("Invalid address hex " + address); + } + + isUserFriendly = false; + wc = wcInternal; + hashPart = Utils.hexToSignedBytes(hexPart); + isTestOnly = false; + isBounceable = false; + isWallet = true; + } else { + isUserFriendly = true; + Address parseResult = parseFriendlyAddress(address); + wc = parseResult.wc; + hashPart = parseResult.hashPart; + isTestOnly = parseResult.isTestOnly; + isBounceable = parseResult.isBounceable; + isWallet = parseResult.isWallet; + } } - int colonIndex = address.indexOf(':'); - if (colonIndex != -1) { - - if (colonIndex != address.lastIndexOf(':')) { - throw new Error("Invalid address " + address); - } - - String wcPart = address.substring(0, colonIndex); - String hexPart = address.substring(colonIndex + 1); - - byte wcInternal = Byte.parseByte(wcPart); - - if (wcInternal != 0 && wcInternal != -1) { - throw new Error("Invalid address wc " + address); - } - - if (hexPart.length() == 63) { - hexPart = "0" + hexPart; - } - if (hexPart.length() == 1) { - hexPart = "000000000000000000000000000000000000000000000000000000000000000"+ hexPart; - } - - if (hexPart.length() != 64) { - throw new Error("Invalid address hex " + address); - } - - isUserFriendly = false; - wc = wcInternal; - hashPart = Utils.hexToSignedBytes(hexPart); - isTestOnly = false; - isBounceable = false; - isWallet = true; - } else { - isUserFriendly = true; - Address parseResult = parseFriendlyAddress(address); - wc = parseResult.wc; - hashPart = parseResult.hashPart; - isTestOnly = parseResult.isTestOnly; - isBounceable = parseResult.isBounceable; - isWallet = parseResult.isWallet; - } - } + public Address(Address address) { - public Address(Address address) { + if (isNull(address)) { + throw new IllegalArgumentException("Address is null"); + } - if (isNull(address)) { - throw new IllegalArgumentException("Address is null"); + wc = address.wc; + hashPart = address.hashPart; + isTestOnly = address.isTestOnly; + isUserFriendly = address.isUserFriendly; + isBounceable = address.isBounceable; + isUrlSafe = address.isUrlSafe; + isWallet = address.isWallet; } - wc = address.wc; - hashPart = address.hashPart; - isTestOnly = address.isTestOnly; - isUserFriendly = address.isUserFriendly; - isBounceable = address.isBounceable; - isUrlSafe = address.isUrlSafe; - isWallet = address.isWallet; - } + public static Address of(String address) { + return new Address(address); + } - public static Address of(String address) { - return new Address(address); - } + public static Address of(byte[] hashCrc) { + return of(bounceable_tag, -1, hashCrc); + } - public static Address of(byte[] hashCrc) { - return of(bounceable_tag, -1, hashCrc); - } + public static Address of(byte flags, int wc, byte[] hashCrc) { + + int flagsByte = flags & 0xff; + boolean isTestOnly = false; + boolean isBounceable; + + if ((flagsByte & test_flag) != 0) { + isTestOnly = true; + flagsByte = (byte) (flagsByte ^ test_flag); + } + if ((flagsByte != bounceable_tag) && (flagsByte != non_bounceable_tag)) { + throw new Error("Unknown address tag"); + } + + byte workchain; + if ((wc & 0xff) == 0xff) { + workchain = -1; + } else { + workchain = (byte) wc; + } + + isBounceable = flagsByte == bounceable_tag; + + Address addr = new Address(); + addr.wc = workchain; + addr.hashPart = hashCrc; + addr.isTestOnly = isTestOnly; + addr.isBounceable = isBounceable; + addr.isWallet = !isBounceable; + return addr; + } - public static Address of(byte flags, int wc, byte[] hashCrc) { + public static Address of(Address address) { + return new Address(address); + } - int flagsByte = flags & 0xff; - boolean isTestOnly = false; - boolean isBounceable; + public String toDecimal() { + return new BigInteger(Utils.bytesToHex(hashPart), 16).toString(10); + } - if ((flagsByte & test_flag) != 0) { - isTestOnly = true; - flagsByte = (byte) (flagsByte ^ test_flag); + public BigInteger toBigInteger() { + return new BigInteger(Utils.bytesToHex(hashPart), 16); } - if ((flagsByte != bounceable_tag) && (flagsByte != non_bounceable_tag)) { - throw new Error("Unknown address tag"); + + public String toHex() { + return Utils.bytesToHex(hashPart); } - byte workchain; - if ((wc & 0xff) == 0xff) { - workchain = -1; - } else { - workchain = (byte) wc; + /** + * Save address to file in 36-byte format + */ + public void saveToFile(String filename) throws IOException { + byte[] wcBytes = ByteBuffer.allocate(4).putInt(wc).array(); + Files.write(Paths.get(filename), Utils.concatBytes(hashPart, wcBytes)); } - isBounceable = flagsByte == bounceable_tag; - - Address addr = new Address(); - addr.wc = workchain; - addr.hashPart = hashCrc; - addr.isTestOnly = isTestOnly; - addr.isBounceable = isBounceable; - addr.isWallet = !isBounceable; - return addr; - } - - public static Address of(Address address) { - return new Address(address); - } - - public String toDecimal() { - return new BigInteger(Utils.bytesToHex(hashPart), 16).toString(10); - } - - public BigInteger toBigInteger() { - return new BigInteger(Utils.bytesToHex(hashPart), 16); - } - - public String toHex() { - return Utils.bytesToHex(hashPart); - } - - /** Save address to file in 36-byte format */ - public void saveToFile(String filename) throws IOException { - byte[] wcBytes = ByteBuffer.allocate(4).putInt(wc).array(); - Files.write(Paths.get(filename), Utils.concatBytes(hashPart, wcBytes)); - } - - /** - * Default flags are: userFriendly=true, UrlSafe=true, Bounceable=true and TestOnly=false - * - * @return String - */ - public String toString() { - return toBounceable(); - } - - public String toString(boolean isUserFriendly) { - return toString(isUserFriendly, isUrlSafe, isBounceable, isTestOnly); - } - - public String toString(boolean isUserFriendly, boolean isUrlSafe) { - return toString(isUserFriendly, isUrlSafe, isBounceable, isTestOnly); - } - - public String toString(boolean isUserFriendly, boolean isUrlSafe, boolean isBounceable) { - return toString(isUserFriendly, isUrlSafe, isBounceable, isTestOnly); - } - - public String toBounceable() { - return toString(true, true, true, false); - } - - public String toBounceableTestnet() { - return toString(true, true, true, true); - } - - public String toRaw() { - return toString(false, true, true, false); - } - - public String toNonBounceable() { - return toString(true, true, false, false); - } - - public String toNonBounceableTestnet() { - return toString(true, true, false, true); - } - - public String toString( - boolean isUserFriendly, boolean isUrlSafe, boolean isBounceable, boolean isTestOnly) { - - if (!isUserFriendly) { - return wc + ":" + Utils.bytesToHex(hashPart); - } else { - int tag = isBounceable ? bounceable_tag : non_bounceable_tag; - if (isTestOnly) { - tag |= test_flag; - } - - byte[] addr = new byte[34]; - byte[] addressWithChecksum = new byte[36]; - addr[0] = (byte) tag; - addr[1] = wc; - - System.arraycopy(hashPart, 0, addr, 2, 32); - - byte[] crc16 = Utils.getCRC16ChecksumAsBytes(addr); - System.arraycopy(addr, 0, addressWithChecksum, 0, 34); - System.arraycopy(crc16, 0, addressWithChecksum, 34, 2); - - String addressBase64 = Utils.bytesToBase64(addressWithChecksum); - - if (isUrlSafe) { - addressBase64 = Utils.bytesToBase64SafeUrl(addressWithChecksum); - } - return addressBase64; + /** + * Default flags are: userFriendly=true, UrlSafe=true, Bounceable=true and TestOnly=false + * + * @return String + */ + public String toString() { + return toBounceable(); } - } - - public static boolean isValid(String address) { - try { - Address.of(address); - return true; - } catch (Throwable e) { - return false; + + public String toString(boolean isUserFriendly) { + return toString(isUserFriendly, isUrlSafe, isBounceable, isTestOnly); } - } - public static Address parseFriendlyAddress(String addressString) { - if (addressString.length() != 48) { - throw new Error("User-friendly address should contain strictly 48 characters"); + public String toString(boolean isUserFriendly, boolean isUrlSafe) { + return toString(isUserFriendly, isUrlSafe, isBounceable, isTestOnly); } - byte[] data = Utils.base64ToBytes(addressString); - if (data.length != 36) { // 1byte tag + 1byte workchain + 32 bytes hash + 2 byte crc - throw new Error("Unknown address type: byte length is not equal to 36"); + + public String toString(boolean isUserFriendly, boolean isUrlSafe, boolean isBounceable) { + return toString(isUserFriendly, isUrlSafe, isBounceable, isTestOnly); } - byte[] addr = Arrays.copyOfRange(data, 0, 34); - byte[] crc = Arrays.copyOfRange(data, 34, 36); + public String toBounceable() { + return toString(true, true, true, false); + } - byte[] calculatedCrc16 = Utils.getCRC16ChecksumAsBytes(addr); + public String toBounceableTestnet() { + return toString(true, true, true, true); + } - if (!(calculatedCrc16[0] == crc[0] && calculatedCrc16[1] == crc[1])) { - throw new Error("Wrong crc16 hashsum"); + public String toRaw() { + return toString(false, true, true, false); } - int tag = addr[0] & 0xff; - boolean isTestOnly = false; - boolean isBounceable = false; - if ((tag & test_flag) != 0) { - isTestOnly = true; - tag = (byte) (tag ^ test_flag); + public String toNonBounceable() { + return toString(true, true, false, false); } - if ((tag != bounceable_tag) && (tag != non_bounceable_tag)) { - throw new Error("Unknown address tag"); + + public String toNonBounceableTestnet() { + return toString(true, true, false, true); } - isBounceable = tag == bounceable_tag; + public String toString( + boolean isUserFriendly, boolean isUrlSafe, boolean isBounceable, boolean isTestOnly) { + + if (!isUserFriendly) { + return wc + ":" + Utils.bytesToHex(hashPart); + } else { + int tag = isBounceable ? bounceable_tag : non_bounceable_tag; + if (isTestOnly) { + tag |= test_flag; + } + + byte[] addr = new byte[34]; + byte[] addressWithChecksum = new byte[36]; + addr[0] = (byte) tag; + addr[1] = wc; + + System.arraycopy(hashPart, 0, addr, 2, 32); + + byte[] crc16 = Utils.getCRC16ChecksumAsBytes(addr); + System.arraycopy(addr, 0, addressWithChecksum, 0, 34); + System.arraycopy(crc16, 0, addressWithChecksum, 34, 2); - byte workchain; - if ((addr[1] & 0xff) == 0xff) { - workchain = -1; - } else { - workchain = addr[1]; + String addressBase64 = Utils.bytesToBase64(addressWithChecksum); + + if (isUrlSafe) { + addressBase64 = Utils.bytesToBase64SafeUrl(addressWithChecksum); + } + return addressBase64; + } } - if (workchain != 0 && workchain != -1) { - throw new Error("Invalid address wc " + workchain); + + public static boolean isValid(String address) { + try { + Address.of(address); + return true; + } catch (Throwable e) { + return false; + } } - byte[] hashPart = Arrays.copyOfRange(addr, 2, 34); + public static Address parseFriendlyAddress(String addressString) { + if (addressString.length() != 48) { + throw new Error("User-friendly address should contain strictly 48 characters"); + } + byte[] data = Utils.base64ToBytes(addressString); + if (data.length != 36) { // 1byte tag + 1byte workchain + 32 bytes hash + 2 byte crc + throw new Error("Unknown address type: byte length is not equal to 36"); + } + + byte[] addr = Arrays.copyOfRange(data, 0, 34); + byte[] crc = Arrays.copyOfRange(data, 34, 36); + + byte[] calculatedCrc16 = Utils.getCRC16ChecksumAsBytes(addr); + + if (!(calculatedCrc16[0] == crc[0] && calculatedCrc16[1] == crc[1])) { + throw new Error("Wrong crc16 hashsum"); + } + int tag = addr[0] & 0xff; + boolean isTestOnly = false; + boolean isBounceable = false; + + if ((tag & test_flag) != 0) { + isTestOnly = true; + tag = (byte) (tag ^ test_flag); + } + if ((tag != bounceable_tag) && (tag != non_bounceable_tag)) { + throw new Error("Unknown address tag"); + } + + isBounceable = tag == bounceable_tag; + + byte workchain; + if ((addr[1] & 0xff) == 0xff) { + workchain = -1; + } else { + workchain = addr[1]; + } + if (workchain != 0 && workchain != -1) { + throw new Error("Invalid address wc " + workchain); + } + + byte[] hashPart = Arrays.copyOfRange(addr, 2, 34); + + Address parsedAddress = new Address(); + parsedAddress.wc = workchain; + parsedAddress.hashPart = hashPart; + parsedAddress.isTestOnly = isTestOnly; + parsedAddress.isBounceable = isBounceable; + parsedAddress.isWallet = !isBounceable; + parsedAddress.addressType = AddressType.STD_ADDRESS; + + return parsedAddress; + } - Address parsedAddress = new Address(); - parsedAddress.wc = workchain; - parsedAddress.hashPart = hashPart; - parsedAddress.isTestOnly = isTestOnly; - parsedAddress.isBounceable = isBounceable; - parsedAddress.isWallet = !isBounceable; - parsedAddress.addressType = AddressType.STD_ADDRESS; + public long getShardAsLong() { + long shardIdxLong = getHash()[0] >> 4; + return BigInteger.valueOf(shardIdxLong).shiftLeft(60).longValue(); + } - return parsedAddress; - } + public BigInteger getShardAsBigInt() { + long shardIdxLong = getHash()[0] >> 4; + return BigInteger.valueOf(shardIdxLong).shiftLeft(60); + } + + public int[] getHash() { + return Utils.signedBytesToUnsigned(hashPart); + } } diff --git a/blockchain/pom.xml b/blockchain/pom.xml index bbcf2069..cf3a5cdd 100644 --- a/blockchain/pom.xml +++ b/blockchain/pom.xml @@ -53,6 +53,11 @@ emulator ${project.parent.version} + + io.github.neodix42 + liteclient + ${project.parent.version} + io.github.neodix42 utils diff --git a/blockchain/src/main/java/org/ton/java/Blockchain.java b/blockchain/src/main/java/org/ton/java/Blockchain.java index 3aa653e9..c0bc9055 100644 --- a/blockchain/src/main/java/org/ton/java/Blockchain.java +++ b/blockchain/src/main/java/org/ton/java/Blockchain.java @@ -1,15 +1,13 @@ package org.ton.java; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - import com.iwebpp.crypto.TweetNaclFast; -import java.math.BigInteger; import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.address.Address; import org.ton.java.cell.Cell; +import org.ton.java.cell.CellBuilder; +import org.ton.java.cell.CellSlice; import org.ton.java.emulator.EmulateTransactionResult; import org.ton.java.emulator.tvm.TvmEmulator; import org.ton.java.emulator.tvm.TvmVerbosityLevel; @@ -18,6 +16,9 @@ import org.ton.java.emulator.tx.TxVerbosityLevel; import org.ton.java.fift.FiftRunner; import org.ton.java.func.FuncRunner; +import org.ton.java.liteclient.LiteClient; +import org.ton.java.liteclient.LiteClientParser; +import org.ton.java.liteclient.api.ResultLastBlock; import org.ton.java.smartcontract.SmartContractCompiler; import org.ton.java.smartcontract.faucet.TestnetFaucet; import org.ton.java.smartcontract.types.WalletV3Config; @@ -27,797 +28,992 @@ import org.ton.java.tlb.types.*; import org.ton.java.tolk.TolkRunner; import org.ton.java.tonlib.Tonlib; +import org.ton.java.tonlib.types.BlockIdExt; import org.ton.java.tonlib.types.*; import org.ton.java.utils.Utils; +import java.math.BigInteger; +import java.util.List; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + @Slf4j @Builder public class Blockchain { - private FuncRunner funcRunner; - private FiftRunner fiftRunner; - private TolkRunner tolkRunner; - private Tonlib tonlib; - private Network network; - VerbosityLevel tonlibVerbosityLevel; - TxEmulatorConfig txEmulatorConfig; - TxVerbosityLevel txEmulatorVerbosityLevel; - TvmVerbosityLevel tvmEmulatorVerbosityLevel; - Contract contract; - String myLocalTonInstallationPath; - String customContractPath; - String customContractAsResource; - Cell customContractDataCell; - Cell customContractBodyCell; - String customGlobalConfigPath; - String customEmulatorPath; - ShardAccount customEmulatorShardAccount; - private static SmartContractCompiler smartContractCompiler; - private static TxEmulator txEmulator; - private static TvmEmulator tvmEmulator; - private static StateInit stateInit; - private static Cell codeCell; - private static Cell dataCell; - - /** default 0.1 toncoin */ - BigInteger initialDeployTopUpAmount; - - public static class BlockchainBuilder {} - - public static BlockchainBuilder builder() { - return new CustomBlockchainBuilder(); - } - - private static class CustomBlockchainBuilder extends BlockchainBuilder { - @Override - public Blockchain build() { - try { - - if (isNull(super.initialDeployTopUpAmount)) { - super.initialDeployTopUpAmount = Utils.toNano(0.1); - } + private FuncRunner funcRunner; + private FiftRunner fiftRunner; + private TolkRunner tolkRunner; + private Tonlib tonlib; + private LiteClient liteClient; + private Network network; + VerbosityLevel tonlibVerbosityLevel; + TxEmulatorConfig txEmulatorConfig; + TxVerbosityLevel txEmulatorVerbosityLevel; + TvmVerbosityLevel tvmEmulatorVerbosityLevel; + Contract contract; + String myLocalTonInstallationPath; + String customContractPath; + String customContractAsResource; + Cell customContractDataCell; + Cell customContractBodyCell; + String customGlobalConfigPath; + /** + * Shows block data next to each transaction. Used for non-emulator deployments. + */ + Boolean printTxBlockData; + Integer customContractWorkchain; + // String customEmulatorPath; + // String customLiteClientPath; + ShardAccount customEmulatorShardAccount; + private static SmartContractCompiler smartContractCompiler; + private static TxEmulator txEmulator; + private static TvmEmulator tvmEmulator; + private static StateInit stateInit; + private static Cell codeCell; + private static Cell dataCell; + + /** + * default 0.1 toncoin + */ + BigInteger initialDeployTopUpAmount; + + public static class BlockchainBuilder { + } - initializeTonlib(); + public static BlockchainBuilder builder() { + return new CustomBlockchainBuilder(); + } - initializeSmartContractCompiler(); + private static class CustomBlockchainBuilder extends BlockchainBuilder { + @Override + public Blockchain build() { + try { - if (super.network != Network.EMULATOR) { - printBlockchainInfo(); - } + if (isNull(super.initialDeployTopUpAmount)) { + super.initialDeployTopUpAmount = Utils.toNano(0.1); + } - compileSmartContract(); + if (isNull(super.printTxBlockData)) { + super.printTxBlockData = true; + } - if (super.network == Network.EMULATOR) { - initializeEmulators(); + if (isNull(super.customContractWorkchain)) { + super.customContractWorkchain = 0; + } + + initializeTonlib(); + + initializeLiteClient(); + + initializeSmartContractCompiler(); + + if (super.network != Network.EMULATOR) { + printBlockchainInfo(); + } + + compileSmartContract(); + + if (super.network == Network.EMULATOR) { + initializeEmulators(); + } + + if (super.network == Network.EMULATOR) { + printBlockchainInfo(); + } + + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Error creating blockchain instance: " + e.getMessage()); + } + return super.build(); } - if (super.network == Network.EMULATOR) { - printBlockchainInfo(); + private void initializeEmulators() { + if (super.network == Network.EMULATOR) { + tvmEmulator = + TvmEmulator.builder() + .codeBoc(codeCell.toBase64()) + .dataBoc(dataCell.toBase64()) + .verbosityLevel(super.tvmEmulatorVerbosityLevel) + .printEmulatorInfo(false) + .build(); + + txEmulator = + TxEmulator.builder() + .verbosityLevel(super.txEmulatorVerbosityLevel) + .printEmulatorInfo(false) + .build(); + + if (isNull(super.customEmulatorShardAccount)) { + super.customEmulatorShardAccount = + ShardAccount.builder() + .account( + Account.builder() + .isNone(false) + .address(MsgAddressIntStd.of(stateInit.getAddress(super.customContractWorkchain))) + .storageInfo( + StorageInfo.builder() + .storageUsed( + StorageUsed.builder() + .cellsUsed(BigInteger.ZERO) + .bitsUsed(BigInteger.ZERO) + .publicCellsUsed(BigInteger.ZERO) + .build()) + .lastPaid(System.currentTimeMillis() / 1000) + .duePayment(BigInteger.ZERO) + .build()) + .accountStorage( + AccountStorage.builder() + .lastTransactionLt(BigInteger.ZERO) + .balance( + CurrencyCollection.builder() + .coins(super.initialDeployTopUpAmount) // initial balance + .build()) + .accountState( + AccountStateActive.builder() + .stateInit( + StateInit.builder() + .code(codeCell) + .data(dataCell) + .build()) + .build()) + .build()) + .build()) + .lastTransHash(BigInteger.ZERO) + .lastTransLt(BigInteger.ZERO) + .build(); + } + } else { + super.contract.setTonlib(super.tonlib); + } } - } catch (Exception e) { - e.printStackTrace(); - throw new Error("Error creating blockchain instance: " + e.getMessage()); - } - return super.build(); - } + private void compileSmartContract() { + if (isNull(super.contract)) { + log.info("Compiling custom smart-contract..."); + if (nonNull(super.customContractAsResource)) { + smartContractCompiler.setContractAsResource(super.customContractAsResource); + } else if (nonNull(super.customContractPath)) { + smartContractCompiler.setContractPath(super.customContractPath); + } else { + throw new Error( + "Specify path to custom contract via customContractAsResource or customContractPath."); + } + + codeCell = smartContractCompiler.compileToCell(); + log.info("Custom smart-contract compiled successfully"); + if (isNull(super.customContractDataCell)) { + throw new Error("Custom contract requires customContractDataCell to be specified."); + } + dataCell = super.customContractDataCell; + } else { + // no need to compile regular smart-contract + log.info("No need to compile regular smart-contract, since it uses precompiled code"); + codeCell = super.contract.createCodeCell(); + dataCell = super.contract.createDataCell(); + } + stateInit = StateInit.builder().code(codeCell).data(dataCell).build(); + } + + private void initializeSmartContractCompiler() { + if (isNull(super.funcRunner)) { + super.funcRunner = FuncRunner.builder().printInfo(false).build(); + } + + if (isNull(super.fiftRunner)) { + super.fiftRunner = FiftRunner.builder().printInfo(false).build(); + } - private void initializeEmulators() { - if (super.network == Network.EMULATOR) { - tvmEmulator = - TvmEmulator.builder() - .codeBoc(codeCell.toBase64()) - .dataBoc(dataCell.toBase64()) - .verbosityLevel(super.tvmEmulatorVerbosityLevel) - .printEmulatorInfo(false) - .build(); - - txEmulator = - TxEmulator.builder() - .verbosityLevel(super.txEmulatorVerbosityLevel) - .printEmulatorInfo(false) - .build(); - - if (isNull(super.customEmulatorShardAccount)) { - super.customEmulatorShardAccount = - ShardAccount.builder() - .account( - Account.builder() - .isNone(false) - .address(MsgAddressIntStd.of(stateInit.getAddress())) - .storageInfo( - StorageInfo.builder() - .storageUsed( - StorageUsed.builder() - .cellsUsed(BigInteger.ZERO) - .bitsUsed(BigInteger.ZERO) - .publicCellsUsed(BigInteger.ZERO) - .build()) - .lastPaid(System.currentTimeMillis() / 1000) - .duePayment(BigInteger.ZERO) - .build()) - .accountStorage( - AccountStorage.builder() - .lastTransactionLt(BigInteger.ZERO) - .balance( - CurrencyCollection.builder() - .coins(super.initialDeployTopUpAmount) // initial balance - .build()) - .accountState( - AccountStateActive.builder() - .stateInit( - StateInit.builder() - .code(codeCell) - .data(dataCell) - .build()) - .build()) - .build()) - .build()) - .lastTransHash(BigInteger.ZERO) - .lastTransLt(BigInteger.ZERO) - .build(); + if (isNull(super.tolkRunner)) { + super.tolkRunner = TolkRunner.builder().printInfo(false).build(); + } + + smartContractCompiler = + SmartContractCompiler.builder() + .fiftRunner(super.fiftRunner) + .funcRunner(super.funcRunner) + .tolkRunner(super.tolkRunner) + .printFiftAsmOutput(false) + .printInfo(false) + .build(); } - } else { - super.contract.setTonlib(super.tonlib); - } - } - private void compileSmartContract() { - if (isNull(super.contract)) { - if (nonNull(super.customContractAsResource)) { - smartContractCompiler.setContractAsResource(super.customContractAsResource); - } else if (nonNull(super.customContractPath)) { - smartContractCompiler.setContractPath(super.customContractPath); - } else { - throw new Error( - "Specify path to custom contract via customContractAsResource or customContractPath."); + private void printBlockchainInfo() { + if (super.network == Network.EMULATOR) { + + log.info( + "Blockchain configuration:\n" + + "Target network: {}\n" + + "Emulator location: {}, configType: {}, txVerbosity: {}, tvmVerbosity: {}\n" + + "Emulator ShardAccount: balance {}, address: {}, lastPaid: {}, lastTransLt: {}\n" + + "Func location: {}\n" + + "Tolk location: {}" + + "Fift location: {}, FIFTPATH={}\n" + + "Contract: {}\n", + super.network, + Utils.detectAbsolutePath("emulator", true), + txEmulator.getConfigType(), + txEmulator.getVerbosityLevel(), + tvmEmulator.getVerbosityLevel(), + Utils.formatNanoValue(super.customEmulatorShardAccount.getBalance()), + super.customEmulatorShardAccount.getAccount().getAddress().toAddress().toBounceable(), + super.customEmulatorShardAccount.getAccount().getStorageInfo().getLastPaid(), + super.customEmulatorShardAccount.getLastTransLt(), + super.funcRunner.getFuncPath(), + super.tolkRunner.getTolkPath(), + super.fiftRunner.getFiftPath(), + super.fiftRunner.getLibsPath(), + nonNull(super.contract) + ? "standard contract " + super.contract.getName() + : isNull(super.customContractPath) + ? "integrated resource " + super.customContractAsResource + : super.customContractPath); + } else { + log.info( + "Blockchain configuration:\n" + + "Target network: {}\n" + + "Emulator not used\n" + + "Tonlib location: {}\n" + + "Tonlib global config: {}\n" + + "Lite-client location: {}\n" + + "Func location: {}\n" + + "Tolk location: {}\n" + + "Fift location: {}, FIFTPATH={}\n" + + "Contract: {}\n", + super.network, + super.tonlib.pathToTonlibSharedLib, + super.tonlib.pathToGlobalConfig, + super.liteClient.getLiteClientPath(), + super.funcRunner.getFuncPath(), + super.tolkRunner.getTolkPath(), + super.fiftRunner.getFiftPath(), + super.fiftRunner.getLibsPath(), + nonNull(super.contract) + ? "standard contract " + super.contract.getName() + : isNull(super.customContractPath) + ? "integrated resource " + super.customContractAsResource + : super.customContractPath); + } } - codeCell = smartContractCompiler.compileToCell(); - if (isNull(super.customContractDataCell)) { - throw new Error("Custom contract requires customContractDataCell to be specified."); + private void initializeLiteClient() { + if (super.network != Network.EMULATOR) { + if (isNull(super.liteClient)) { + if (StringUtils.isNotEmpty(super.customGlobalConfigPath)) { + super.liteClient = + LiteClient.builder() + .pathToGlobalConfig(super.customGlobalConfigPath) + .printInfo(false) + .build(); + } else if (super.network == Network.MAINNET) { + super.liteClient = LiteClient.builder().testnet(false).printInfo(true).build(); + } else if (super.network == Network.TESTNET) { + super.liteClient = LiteClient.builder().testnet(true).printInfo(true).build(); + } else { // MyLocalTon + super.liteClient = + LiteClient.builder() + .pathToGlobalConfig( + super.myLocalTonInstallationPath + "/genesis/db/my-ton-global.config.json") + .printInfo(false) + .build(); + } + } + } + } + + private void initializeTonlib() { + if (super.network != Network.EMULATOR) { + if (isNull(super.tonlib)) { + if (StringUtils.isNotEmpty(super.customGlobalConfigPath)) { + super.tonlib = + Tonlib.builder() + .ignoreCache(false) + .pathToGlobalConfig(super.customGlobalConfigPath) + .printInfo(false) + .build(); + } else if (super.network == Network.MAINNET) { + super.tonlib = + Tonlib.builder() + .testnet(false) + .ignoreCache(false) + .verbosityLevel( + nonNull(super.tonlibVerbosityLevel) + ? super.tonlibVerbosityLevel + : VerbosityLevel.INFO) + .printInfo(false) + .build(); + } else if (super.network == Network.TESTNET) { + super.tonlib = + Tonlib.builder() + .testnet(true) + .ignoreCache(false) + .verbosityLevel( + nonNull(super.tonlibVerbosityLevel) + ? super.tonlibVerbosityLevel + : VerbosityLevel.INFO) + .printInfo(false) + .build(); + + } else { // MyLocalTon + if (StringUtils.isNotEmpty(super.myLocalTonInstallationPath)) { + super.tonlib = + Tonlib.builder() + .ignoreCache(false) + .verbosityLevel( + nonNull(super.tonlibVerbosityLevel) + ? super.tonlibVerbosityLevel + : VerbosityLevel.INFO) + .pathToGlobalConfig( + super.myLocalTonInstallationPath + + "/genesis/db/my-ton-global.config.json") + .printInfo(false) + .build(); + } else { + throw new Error( + "When using MyLocalTon network myLocalTonInstallationPath must bet set."); + } + } + } + } } - dataCell = super.customContractDataCell; - } else { - // no need to compile regular smart-contract - codeCell = super.contract.createCodeCell(); - dataCell = super.contract.createDataCell(); - } - stateInit = StateInit.builder().code(codeCell).data(dataCell).build(); } - private void initializeSmartContractCompiler() { - if (isNull(super.funcRunner)) { - super.funcRunner = FuncRunner.builder().printInfo(false).build(); - } - - if (isNull(super.fiftRunner)) { - super.fiftRunner = FiftRunner.builder().printInfo(false).build(); - } - - if (isNull(super.tolkRunner)) { - super.tolkRunner = TolkRunner.builder().printInfo(false).build(); - } - - smartContractCompiler = - SmartContractCompiler.builder() - .fiftRunner(super.fiftRunner) - .funcRunner(super.funcRunner) - .tolkRunner(super.tolkRunner) - .printFiftAsmOutput(false) - .printInfo(false) - .build(); + /** + * There is a huge difference between sendExternal(Cell body) and + * sendExternal(Message message). The first one sends a body to itself, the second one - + * sends the message to a destination specified in a message. Basically, in the first method we + * construct MsgUtils.createExternalMessage() with replaced destination address. + * + * @param body Cell + * @return SendExternalResult + */ + public SendExternalResult sendExternal(Cell body) { + Address address = getAddr(); + Message message = MsgUtils.createExternalMessage(address, null, body); + //log.info("message cell-hash {}", Utils.bytesToHex(message.toCell().getHash())); + return sendExternal(message); } - private void printBlockchainInfo() { - if (super.network == Network.EMULATOR) { + /** + * There is a huge difference between sendExternal(Cell body) and + * sendExternal(Message message). The first one sends a body to itself, the second one - + * sends the message to a destination specified in a message. Basically, in the first method we + * construct MsgUtils.createExternalMessage() with replaced destination address. + * + * @param body Cell + * @return SendExternalResult + */ + public SendExternalResult sendExternal(Cell body, int pauseInSeconds) { + Address address = getAddr(); + Message message = MsgUtils.createExternalMessage(address, null, body); + //log.info("message cell-hash {}", Utils.bytesToHex(message.toCell().getHash())); + return sendExternal(message, pauseInSeconds); + } - log.info( - "Blockchain configuration:\n" - + "Target network: {}\n" - + "Emulator location: {}, configType: {}, txVerbosity: {}, tvmVerbosity: {}\n" - + "Emulator ShardAccount: balance {}, address: {}, lastPaid: {}, lastTransLt: {}\n" - + "Func location: {}\n" - + "Tolk location: {}" - + "Fift location: {}, FIFTPATH={}\n" - + "Contract: {}\n", - super.network, - Utils.detectAbsolutePath("emulator", true), - txEmulator.getConfigType(), - txEmulator.getVerbosityLevel(), - tvmEmulator.getVerbosityLevel(), - Utils.formatNanoValue(super.customEmulatorShardAccount.getBalance()), - super.customEmulatorShardAccount.getAccount().getAddress().toAddress().toBounceable(), - super.customEmulatorShardAccount.getAccount().getStorageInfo().getLastPaid(), - super.customEmulatorShardAccount.getLastTransLt(), - super.funcRunner.getFuncPath(), - super.tolkRunner.getTolkPath(), - super.fiftRunner.getFiftPath(), - super.fiftRunner.getLibsPath(), - nonNull(super.contract) - ? "standard contract " + super.contract.getName() - : isNull(super.customContractPath) - ? "integrated resource " + super.customContractAsResource - : super.customContractPath); - } else { - log.info( - "Blockchain configuration:\n" - + "Target network: {}\n" - + "Emulator not used\n" - + "Tonlib location: {}\n" - + "Tonlib global config: {}\n" - + "Func location: {}\n" - + "Tolk location: {}\n" - + "Fift location: {}, FIFTPATH={}\n" - + "Contract: {}\n", - super.network, - super.tonlib.pathToTonlibSharedLib, - super.tonlib.pathToGlobalConfig, - super.funcRunner.getFuncPath(), - super.tolkRunner.getTolkPath(), - super.fiftRunner.getFiftPath(), - super.fiftRunner.getLibsPath(), - nonNull(super.contract) - ? "standard contract " + super.contract.getName() - : isNull(super.customContractPath) - ? "integrated resource " + super.customContractAsResource - : super.customContractPath); - } + + /** + * Sends external Message and returns its hash upon sending + * + * @param message Message + * @return SendExternalResult + */ + public SendExternalResult sendExternal(Message message) { + return sendExternal(message, -1); } - private void initializeTonlib() { - if (super.network != Network.EMULATOR) { - if (isNull(super.tonlib)) { - if (StringUtils.isNotEmpty(super.customGlobalConfigPath)) { - super.tonlib = - Tonlib.builder() - .ignoreCache(false) - .pathToGlobalConfig(super.customGlobalConfigPath) - .printInfo(false) - .build(); - } else if (super.network == Network.MAINNET) { - super.tonlib = - Tonlib.builder() - .testnet(false) - .ignoreCache(false) - .verbosityLevel( - nonNull(super.tonlibVerbosityLevel) - ? super.tonlibVerbosityLevel - : VerbosityLevel.INFO) - .printInfo(false) - .build(); - } else if (super.network == Network.TESTNET) { - super.tonlib = - Tonlib.builder() - .testnet(true) - .ignoreCache(false) - .verbosityLevel( - nonNull(super.tonlibVerbosityLevel) - ? super.tonlibVerbosityLevel - : VerbosityLevel.INFO) - .printInfo(false) - .build(); - - } else { // MyLocalTon - if (StringUtils.isNotEmpty(super.myLocalTonInstallationPath)) { - super.tonlib = - Tonlib.builder() - .ignoreCache(false) - .verbosityLevel( - nonNull(super.tonlibVerbosityLevel) - ? super.tonlibVerbosityLevel - : VerbosityLevel.INFO) - .pathToGlobalConfig( - super.myLocalTonInstallationPath - + "/genesis/db/my-ton-global.config.json") - .printInfo(false) - .build(); - } else { - throw new Error( - "When using MyLocalTon network myLocalTonInstallationPath must bet set."); + + /** + * Sends external Message then waits pauseInSeconds and prints out account's transactions. + * + * @param message Message + * @param pauseInSeconds int + * @return SendExternalResult + */ + public SendExternalResult sendExternal(Message message, int pauseInSeconds) { + + try { + + Address address = getAddr(); + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + if (network != Network.EMULATOR) { + String initialBalance = Utils.formatNanoValue(tonlib.getAccountBalance(address)); +// log.info("initialBalance {}", initialBalance); + log.info( + "Sending external message (cell-hash: {}) to bounceable address {} on {}...", + message.toCell().getShortHash(), + bounceableAddress, + network); + ExtMessageInfo tonlibResult = tonlib.sendRawMessage(message.toCell().toBase64()); + + if (tonlibResult.getError().getCode() != 0) { + throw new Error( + "Cannot send external message on " + + network + + ". Error code: " + + tonlibResult.getError().getCode()); + } else { + log.info("Successfully sent external message on {}", network); + + if (pauseInSeconds != -1) { + waitForTx(initialBalance, pauseInSeconds); + } + + return SendExternalResult.builder().tonlibResult(tonlibResult).build(); + } + + } else { // emulator + + String initialBalance = Utils.formatNanoValue(customEmulatorShardAccount.getBalance()); + + log.info( + "Sending external message (cell-hash: {}) to bounceable address {} on {}...", + message.toCell().getShortHash(), + bounceableAddress, + network); + EmulateTransactionResult emulateTransactionResult = + txEmulator.emulateTransaction( + customEmulatorShardAccount.toCell().toBase64(), message.toCell().toBase64()); + + if (emulateTransactionResult.isSuccess() + && emulateTransactionResult.getVm_exit_code() == 0) { + customEmulatorShardAccount = emulateTransactionResult.getNewShardAccount(); + log.info("Successfully emulated external message on {}", network); + + // reinit TVM emulator with a new stateInit + tvmEmulator = + TvmEmulator.builder() + .codeBoc(emulateTransactionResult.getNewStateInit().getCode().toBase64()) + .dataBoc(emulateTransactionResult.getNewStateInit().getData().toBase64()) + .verbosityLevel(tvmEmulatorVerbosityLevel) + .printEmulatorInfo(false) + .build(); + + emulateTransactionResult + .getTransaction() + .printTransactionInfo(true, true, initialBalance); + log.info( + "final balance {}", + Utils.formatNanoValue(emulateTransactionResult.getNewShardAccount().getBalance())); + emulateTransactionResult.getTransaction().printAllMessages(true, true); + } else { + log.error( + "Cannot emulate transaction. Error: " + + emulateTransactionResult.getError() + + ", VM exit code: " + + emulateTransactionResult.getVm_exit_code()); + } + return SendExternalResult.builder().emulatorResult(emulateTransactionResult).build(); } - } + + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Cannot send external message on " + network + ". Error " + e.getMessage()); } - } } - } - - /** - * There is a huge difference between sendExternal(Cell body) and sendExternal(Message message). - * The first one sends a body to itself, the second one - sends the message to a destination specified in a message. - * Basically, in the first method we construct MsgUtils.createExternalMessage() with replaced destination address. - * - * @param body Cell - * @return SendExternalResult - */ - public SendExternalResult sendExternal(Cell body) { - Address address; - if (nonNull(contract)) { - address = contract.getAddress(); - } else { - address = stateInit.getAddress(); + + /** + * Deploys regular or custom smart-contract at: default workchain (0) for custom smart-contract, + * and at specified (contract.wc) workchain for regular contract. + * + * @param waitForDeploymentSeconds int + * @return boolean True if address was topped up AND a send message with stateInit was sent successfully AND if + * account state has changed. + */ + public boolean deploy(int waitForDeploymentSeconds) { + try { + if (nonNull(contract)) { + deployRegularContract(contract, waitForDeploymentSeconds); + } else { // deploy on emulator custom contract + deployCustomContract(stateInit, waitForDeploymentSeconds); + } + if (network == Network.EMULATOR) { + log.info("Deployed on {}", network); + } + return true; + } catch (Exception e) { + log.error("Cannot deploy the contract on " + network + ". Error " + e.getMessage()); + e.printStackTrace(); + return false; + } } - Message message = MsgUtils.createExternalMessage(address, null, body); - return sendExternal(message); - } - - public SendExternalResult sendExternal(Message message) { - - try { - - Address address; - if (nonNull(contract)) { - address = contract.getAddress(); - } else { - address = stateInit.getAddress(); - } - String bounceableAddress = - (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); - - if (network != Network.EMULATOR) { - String initialBalance = Utils.formatNanoValue(tonlib.getAccountBalance(address)); - log.info("initialBalance {}", initialBalance); - log.info( - "Sending external message (cell-hash: {}) to bounceable address {} on {}...", - message.toCell().getShortHash(), - bounceableAddress, - network); - ExtMessageInfo tonlibResult = tonlib.sendRawMessage(message.toCell().toBase64()); - if (tonlibResult.getError().getCode() != 0) { - throw new Error( - "Cannot send external message on " - + network - + ". Error code: " - + tonlibResult.getError().getCode()); + + public GetterResult runGetMethod(String methodName) { + Address address = getAddr(); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + methodName, + address.toBounceable(), + network); + + GetterResult result = + GetterResult.builder().emulatorResult(tvmEmulator.runGetMethod(methodName)).build(); + if (result.getEmulatorResult().getVm_exit_code() != 0) { + throw new Error( + "Cannot execute run method (" + + methodName + + "), Error:" + + result.getEmulatorResult().getVm_log()); + } + return result; } else { - log.info("Successfully sent external message on {}", network); + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info("Running GetMethod {} against {} on {}...", methodName, bounceableAddress, network); + + return GetterResult.builder().tonlibResult(tonlib.runMethod(address, methodName)).build(); } - return SendExternalResult.builder().tonlibResult(tonlibResult).build(); - } else { // emulator + } - String initialBalance = Utils.formatNanoValue(customEmulatorShardAccount.getBalance()); + public BigInteger runGetSeqNo() { + Address address = getAddr(); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + "seqno", + address.toBounceable(), + network); + return tvmEmulator.runGetSeqNo(); - log.info( - "Sending external message (cell-hash: {}) to bounceable address {} on {}...", - message.toCell().getShortHash(), - bounceableAddress, network); - EmulateTransactionResult emulateTransactionResult = - txEmulator.emulateTransaction( - customEmulatorShardAccount.toCell().toBase64(), message.toCell().toBase64()); - if (emulateTransactionResult.isSuccess() - && emulateTransactionResult.getVm_exit_code() == 0) { - customEmulatorShardAccount = emulateTransactionResult.getNewShardAccount(); - log.info("Successfully emulated external message on {}", network); - - // reinit TVM emulator with a new stateInit - tvmEmulator = - TvmEmulator.builder() - .codeBoc(emulateTransactionResult.getNewStateInit().getCode().toBase64()) - .dataBoc(emulateTransactionResult.getNewStateInit().getData().toBase64()) - .verbosityLevel(tvmEmulatorVerbosityLevel) - .printEmulatorInfo(false) - .build(); - - emulateTransactionResult - .getTransaction() - .printTransactionFees(true, true, initialBalance); - log.info( - "final balance {}", - Utils.formatNanoValue(emulateTransactionResult.getNewShardAccount().getBalance())); - emulateTransactionResult.getTransaction().printAllMessages(true); } else { - log.error( - "Cannot emulate transaction. Error: " - + emulateTransactionResult.getError() - + ", VM exit code: " - + emulateTransactionResult.getVm_exit_code()); + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info("Running GetMethod {} against {} on {}...", "seqno", bounceableAddress, network); + RunResult result = tonlib.runMethod(address, "seqno"); + if (result.getExit_code() != 0) { + throw new Error( + "Cannot get seqno from contract " + + bounceableAddress + + ", exitCode " + + result.getExit_code()); + } + TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); + + return seqno.getNumber(); } - return SendExternalResult.builder().emulatorResult(emulateTransactionResult).build(); - } + } - } catch (Exception e) { - e.printStackTrace(); - throw new Error("Cannot send external message on " + network + ". Error " + e.getMessage()); + private Address getAddr() { + if (nonNull(contract)) { + return contract.getAddress(); + } else { + return stateInit.getAddress(customContractWorkchain); + } } - } - - public boolean deploy(int waitForDeploymentSeconds) { - try { - if (nonNull(contract)) { - deployRegularContract(contract, waitForDeploymentSeconds); - } else { // deploy on emulator custom contract - deployCustomContract(stateInit, waitForDeploymentSeconds); - } - if (network == Network.EMULATOR) { - log.info("Deployed on {}", network); - } - return true; - } catch (Exception e) { - log.error("Cannot deploy the contract on " + network + ". Error " + e.getMessage()); - e.printStackTrace(); - return false; + + public String runGetPublicKey() { + Address address = getAddr(); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + "get_public_key", + address.toBounceable(), + network); + return tvmEmulator.runGetPublicKey(); + } else { + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info( + "Running GetMethod {} against {} on {}...", "get_public_key", bounceableAddress, network); + RunResult result = tonlib.runMethod(address, "get_public_key"); + if (result.getExit_code() != 0) { + throw new Error( + "Cannot get_public_key from contract " + + bounceableAddress + + ", exitCode " + + result.getExit_code()); + } + TvmStackEntryNumber publicKeyNumber = (TvmStackEntryNumber) result.getStack().get(0); + return publicKeyNumber.getNumber().toString(16); + } } - } - - public GetterResult runGetMethod(String methodName) { - - if (network == Network.EMULATOR) { - log.info( - "Running GetMethod {} against {} on {}...", - methodName, - stateInit.getAddress().toBounceable(), - network); - - GetterResult result = - GetterResult.builder().emulatorResult(tvmEmulator.runGetMethod(methodName)).build(); - if (result.getEmulatorResult().getVm_exit_code() != 0) { - throw new Error( - "Cannot execute run method (" - + methodName - + "), Error:" - + result.getEmulatorResult().getVm_log()); - } - return result; - } else { - Address address; - if (nonNull(contract)) { - address = contract.getAddress(); - } else { - address = stateInit.getAddress(); - } - String bounceableAddress = - (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); - - log.info("Running GetMethod {} against {} on {}...", methodName, bounceableAddress, network); - - return GetterResult.builder().tonlibResult(tonlib.runMethod(address, methodName)).build(); + + public BigInteger runGetSubWalletId() { + Address address = getAddr(); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + "get_subwallet_id", + address.toBounceable(), + network); + return tvmEmulator.runGetSubWalletId(); + } else { + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info( + "Running GetMethod {} against {} on {}...", + "get_subwallet_id", + bounceableAddress, + network); + RunResult result = tonlib.runMethod(address, "get_subwallet_id"); + if (result.getExit_code() != 0) { + throw new Error( + "Cannot get_subwallet_id from contract " + + bounceableAddress + + ", exitCode " + + result.getExit_code()); + } + TvmStackEntryNumber subWalletId = (TvmStackEntryNumber) result.getStack().get(0); + + return subWalletId.getNumber(); + } } - } - - public BigInteger runGetSeqNo() { - if (network == Network.EMULATOR) { - log.info( - "Running GetMethod {} against {} on {}...", - "seqno", - stateInit.getAddress().toBounceable(), - network); - return tvmEmulator.runGetSeqNo(); - - } else { - Address address; - if (nonNull(contract)) { - address = contract.getAddress(); - } else { - address = stateInit.getAddress(); - } - String bounceableAddress = - (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); - - log.info("Running GetMethod {} against {} on {}...", "seqno", bounceableAddress, network); - RunResult result = tonlib.runMethod(address, "seqno"); - if (result.getExit_code() != 0) { - throw new Error( - "Cannot get seqno from contract " - + bounceableAddress - + ", exitCode " - + result.getExit_code()); - } - TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); - - return seqno.getNumber(); + + private BigInteger topUpFromMyLocalTonFaucet(Address address) { + ExtMessageInfo result; + String nonBounceableAddress; + WalletV3R2 faucetMyLocalTonWallet = + WalletV3R2.builder() + .tonlib(tonlib) + .walletId(42) + .keyPair( + TweetNaclFast.Signature.keyPair_fromSeed( + Utils.hexToSignedBytes( + "44e67357b8e3333b617eb62f759890c95a6bb3cc95557ba60b80b8619f8b7c9d"))) + .build(); + log.info("faucetMyLocalTonWallet address {}", faucetMyLocalTonWallet.getAddress().toRaw()); + + log.info("myLocalTon faucet balance {}", faucetMyLocalTonWallet.getBalance()); + nonBounceableAddress = address.toNonBounceable(); + log.info( + "Topping up ({}) with {} toncoin from MyLocalTon Faucet", + nonBounceableAddress, + Utils.formatNanoValue(initialDeployTopUpAmount)); + + WalletV3Config walletV3Config = + WalletV3Config.builder() + .bounce(false) + .walletId(42) + .seqno(tonlib.getSeqno(faucetMyLocalTonWallet.getAddress())) + .destination(address) + .amount(initialDeployTopUpAmount) + .comment("top-up from ton4j faucet") + .build(); + + result = faucetMyLocalTonWallet.send(walletV3Config); + + if (result.getError().getCode() != 0) { + throw new Error( + "Cannot send external message to " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); + } + + tonlib.waitForBalanceChange(address, 20); + return tonlib.getAccountBalance(address); } - } - - public String runGetPublicKey() { - - if (network == Network.EMULATOR) { - log.info( - "Running GetMethod {} against {} on {}...", - "get_public_key", - stateInit.getAddress().toBounceable(), - network); - return tvmEmulator.runGetPublicKey(); - } else { - Address address; - if (nonNull(contract)) { - address = contract.getAddress(); - } else { - address = stateInit.getAddress(); - } - String bounceableAddress = - (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); - - log.info( - "Running GetMethod {} against {} on {}...", "get_public_key", bounceableAddress, network); - RunResult result = tonlib.runMethod(address, "get_public_key"); - if (result.getExit_code() != 0) { - throw new Error( - "Cannot get_public_key from contract " - + bounceableAddress - + ", exitCode " - + result.getExit_code()); - } - TvmStackEntryNumber publicKeyNumber = (TvmStackEntryNumber) result.getStack().get(0); - return publicKeyNumber.getNumber().toString(16); + + private void deployRegularContract(Contract contract, int waitForDeploymentSeconds) + throws InterruptedException { + Address address = contract.getAddress(); + log.info("Deploying {} ({}) on {}...", contract.getName(), address.toRaw(), network); + // contract.getTonlib(); + if (network != Network.EMULATOR) { + ExtMessageInfo result; + + if (waitForDeploymentSeconds != 0) { + + if (network == Network.MAINNET) { + String nonBounceableAddress = address.toNonBounceable(); + log.info( + "Waiting {}s for toncoins to be deposited to address {} ({})", + waitForDeploymentSeconds, + nonBounceableAddress, + address.toRaw()); + tonlib.waitForBalanceChange(address, waitForDeploymentSeconds); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); + Message msg = contract.prepareDeployMsg(); + result = tonlib.sendRawMessage(msg.toCell().toBase64()); + assert result.getError().getCode() != 0; + tonlib.waitForDeployment(address, waitForDeploymentSeconds); + log.info( + "{} deployed at non-bounceable address {} ({})", + contract.getName(), + nonBounceableAddress, + address.toBounceable()); + } else if (network == Network.TESTNET) { + String nonBounceableAddress = address.toNonBounceableTestnet(); + log.info( + "Topping up {} with {} toncoin from TestnetFaucet", + nonBounceableAddress, + Utils.formatNanoValue(initialDeployTopUpAmount)); + BigInteger newBalance = + TestnetFaucet.topUpContract(tonlib, contract.getAddress(), initialDeployTopUpAmount); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); + Message msg = contract.prepareDeployMsg(); + result = tonlib.sendRawMessage(msg.toCell().toBase64()); + if (result.getError().getCode() != 0) { + throw new Error( + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); + } + + tonlib.waitForDeployment(address, waitForDeploymentSeconds); + log.info( + "{} deployed at bounceable address {} ({})", + contract.getName(), + address.toBounceableTestnet(), + address.toRaw()); + } else { // myLocalTon + String nonBounceableAddress = address.toNonBounceable(); + // top up first + BigInteger newBalance = topUpFromMyLocalTonFaucet(address); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); + // deploy smc + Message msg = contract.prepareDeployMsg(); + result = tonlib.sendRawMessage(msg.toCell().toBase64()); + if (result.getError().getCode() != 0) { + throw new Error( + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); + } + + tonlib.waitForDeployment(address, waitForDeploymentSeconds); + log.info( + "{} deployed at bounceable address {} ({})", + contract.getName(), + address.toBounceable(), + address.toRaw()); + } + } + } } - } - - public BigInteger runGetSubWalletId() { - - if (network == Network.EMULATOR) { - log.info( - "Running GetMethod {} against {} on {}...", - "get_subwallet_id", - stateInit.getAddress().toBounceable(), - network); - return tvmEmulator.runGetSubWalletId(); - } else { - Address address; - if (nonNull(contract)) { - address = contract.getAddress(); - } else { - address = stateInit.getAddress(); - } - String bounceableAddress = - (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); - - log.info( - "Running GetMethod {} against {} on {}...", - "get_subwallet_id", - bounceableAddress, - network); - RunResult result = tonlib.runMethod(address, "get_subwallet_id"); - if (result.getExit_code() != 0) { - throw new Error( - "Cannot get_subwallet_id from contract " - + bounceableAddress - + ", exitCode " - + result.getExit_code()); - } - TvmStackEntryNumber subWalletId = (TvmStackEntryNumber) result.getStack().get(0); - - return subWalletId.getNumber(); + + + /** + * custom contract does not have conventional deploy methods + */ + private void deployCustomContract(StateInit stateInit, int waitForDeploymentSeconds) + throws InterruptedException { + String contractName = + isNull(customContractAsResource) ? customContractPath : customContractAsResource; + Address address = stateInit.getAddress(customContractWorkchain); + log.info("Deploying {} on {}...", address.toRaw(), network); + if (network != Network.EMULATOR) { + ExtMessageInfo result; + + if (waitForDeploymentSeconds != 0) { + + if (network == Network.MAINNET) { + String nonBounceableAddress = address.toNonBounceable(); + + log.info( + "Waiting {}s for toncoins to be deposited to non-bounceable address {}", + waitForDeploymentSeconds, + nonBounceableAddress); + tonlib.waitForBalanceChange(address, waitForDeploymentSeconds); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); + Message msg = + MsgUtils.createExternalMessage( + address, + stateInit, + isNull(customContractBodyCell) ? stateInit.getData() : customContractDataCell); + result = tonlib.sendRawMessage(msg.toCell().toBase64()); + assert result.getError().getCode() != 0; + tonlib.waitForDeployment(address, waitForDeploymentSeconds); + log.info( + "{} deployed at bounceable address {} ({})", + contractName, + address.toBounceable(), + address.toRaw()); + } else if (network == Network.TESTNET) { + String nonBounceableAddress = address.toNonBounceableTestnet(); + log.info( + "Topping up non-bounceable {} with {} toncoin from TestnetFaucet", + nonBounceableAddress, + Utils.formatNanoValue(initialDeployTopUpAmount)); + + BigInteger newBalance = + TestnetFaucet.topUpContract(tonlib, address, initialDeployTopUpAmount); + + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); + Message msg = + MsgUtils.createExternalMessage( + address, + stateInit, + isNull(customContractBodyCell) ? stateInit.getData() : customContractDataCell); + result = tonlib.sendRawMessage(msg.toCell().toBase64()); + if (result.getError().getCode() != 0) { + throw new Error( + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); + } + + tonlib.waitForDeployment(address, waitForDeploymentSeconds); + log.info( + "{} deployed at bounceable address {} ({})", + contractName, + address.toBounceableTestnet(), + address.toRaw()); + } else { // myLocalTon + String nonBounceableAddress = address.toNonBounceable(); + // top up first + BigInteger newBalance = topUpFromMyLocalTonFaucet(address); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); + // deploy smc + Message msg = + MsgUtils.createExternalMessage( + address, + stateInit, + isNull(customContractBodyCell) ? stateInit.getData() : customContractDataCell); + result = tonlib.sendRawMessage(msg.toCell().toBase64()); + if (result.getError().getCode() != 0) { + throw new Error( + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); + } + + tonlib.waitForDeployment(address, waitForDeploymentSeconds); + log.info( + "{} deployed at bounceable address {} ({})", + contractName, + address.toBounceable(), + address.toRaw()); + } + } + } } - } - - private BigInteger topUpFromMyLocalTonFaucet(Address address) { - ExtMessageInfo result; - String nonBounceableAddress; - WalletV3R2 faucetMyLocalTonWallet = - WalletV3R2.builder() - .tonlib(tonlib) - .walletId(42) - .keyPair( - TweetNaclFast.Signature.keyPair_fromSeed( - Utils.hexToSignedBytes( - "44e67357b8e3333b617eb62f759890c95a6bb3cc95557ba60b80b8619f8b7c9d"))) - .build(); - log.info("faucetMyLocalTonWallet address {}", faucetMyLocalTonWallet.getAddress().toRaw()); - - log.info("myLocalTon faucet balance {}", faucetMyLocalTonWallet.getBalance()); - nonBounceableAddress = address.toNonBounceable(); - log.info( - "Topping up ({}) with {} toncoin from MyLocalTon Faucet", - nonBounceableAddress, - Utils.formatNanoValue(initialDeployTopUpAmount)); - - WalletV3Config walletV3Config = - WalletV3Config.builder() - .bounce(false) - .walletId(42) - .seqno(tonlib.getSeqno(faucetMyLocalTonWallet.getAddress())) - .destination(address) - .amount(initialDeployTopUpAmount) - .comment("top-up from ton4j faucet") - .build(); - - result = faucetMyLocalTonWallet.send(walletV3Config); - - if (result.getError().getCode() != 0) { - throw new Error( - "Cannot send external message to " - + nonBounceableAddress - + ". Error: " - + result.getError().getMessage()); + + public void waitForTx(String initialBalance, int pauseInSeconds) { + + if (network != Network.EMULATOR) { + Address address = getAddr(); + + RawTransactions rawTransactions = null; + while (isNull(rawTransactions)) { + Utils.sleep(pauseInSeconds); + rawTransactions = tonlib.getRawTransactions(address.toRaw(), null, null); + +// log.info("total txs: {}", rawTransactions.getTransactions().size()); + + if (printTxBlockData) { + Transaction.printTxHeader(""); + for (RawTransaction tx : rawTransactions.getTransactions()) { + Transaction transaction = Transaction.deserialize(CellSlice.beginParse(CellBuilder.beginCell().fromBocBase64(tx.getData()).endCell())); + BlockIdExt block = tonlib.lookupBlock(0, address.wc, address.getShardAsLong(), 0, transaction.getNow()); + transaction.printTransactionInfo(false, false, initialBalance, block.getShortBlockSeqno()); + + } + Transaction.printTxFooter(); + } else { + Transaction.printTxHeaderWithoutBlock(""); + for (RawTransaction tx : rawTransactions.getTransactions()) { + Transaction transaction = Transaction.deserialize(CellSlice.beginParse(CellBuilder.beginCell().fromBocBase64(tx.getData()).endCell())); + transaction.printTransactionInfo(false, false, initialBalance); + } + Transaction.printTxFooterWithoutBlock(); + } + + MessagePrintInfo.printMessageInfoHeader(); + + for (RawTransaction txa : rawTransactions.getTransactions()) { + Transaction transactiona = Transaction.deserialize(CellSlice.beginParse(CellBuilder.beginCell().fromBocBase64(txa.getData()).endCell())); + transactiona.printAllMessages(false); + } + MessagePrintInfo.printMessageInfoFooter(); + } + } } - tonlib.waitForBalanceChange(address, 20); - return tonlib.getAccountBalance(address); - } - - private void deployRegularContract(Contract contract, int waitForDeploymentSeconds) - throws InterruptedException { - Address address = contract.getAddress(); - log.info("Deploying {} ({}) on {}...", contract.getName(), address.toRaw(), network); - // contract.getTonlib(); - if (network != Network.EMULATOR) { - ExtMessageInfo result; - - if (waitForDeploymentSeconds != 0) { - - if (network == Network.MAINNET) { - String nonBounceableAddress = address.toNonBounceable(); - log.info( - "Waiting {}s for toncoins to be deposited to address {} ({})", - waitForDeploymentSeconds, - nonBounceableAddress, - address.toRaw()); - tonlib.waitForBalanceChange(address, waitForDeploymentSeconds); - log.info( - "Sending external message to non-bounceable address {} with deploy instructions...", - nonBounceableAddress); - Message msg = contract.prepareDeployMsg(); - result = tonlib.sendRawMessage(msg.toCell().toBase64()); - assert result.getError().getCode() != 0; - tonlib.waitForDeployment(address, waitForDeploymentSeconds); - log.info( - "{} deployed at non-bounceable address {} ({})", - contract.getName(), - nonBounceableAddress, - address.toBounceable()); - } else if (network == Network.TESTNET) { - String nonBounceableAddress = address.toNonBounceableTestnet(); - log.info( - "Topping up {} with {} toncoin from TestnetFaucet", - nonBounceableAddress, - Utils.formatNanoValue(initialDeployTopUpAmount)); - BigInteger newBalance = - TestnetFaucet.topUpContract(tonlib, contract.getAddress(), initialDeployTopUpAmount); - log.info( - "Topped up ({}) successfully, new balance {}", - nonBounceableAddress, - Utils.formatNanoValue(newBalance)); - log.info( - "Sending external message to non-bounceable address {} with deploy instructions...", - nonBounceableAddress); - Message msg = contract.prepareDeployMsg(); - result = tonlib.sendRawMessage(msg.toCell().toBase64()); - if (result.getError().getCode() != 0) { - throw new Error( - "Cannot send external message to non-bounceable address " - + nonBounceableAddress - + ". Error: " - + result.getError().getMessage()); - } - - tonlib.waitForDeployment(address, waitForDeploymentSeconds); - log.info( - "{} deployed at bounceable address {} ({})", - contract.getName(), - address.toBounceableTestnet(), - address.toRaw()); - } else { // myLocalTon - String nonBounceableAddress = address.toNonBounceable(); - // top up first - BigInteger newBalance = topUpFromMyLocalTonFaucet(address); - log.info( - "Topped up ({}) successfully, new balance {}", - nonBounceableAddress, - Utils.formatNanoValue(newBalance)); - // deploy smc - Message msg = contract.prepareDeployMsg(); - result = tonlib.sendRawMessage(msg.toCell().toBase64()); - if (result.getError().getCode() != 0) { - throw new Error( - "Cannot send external message to non-bounceable address " - + nonBounceableAddress - + ". Error: " - + result.getError().getMessage()); - } - - tonlib.waitForDeployment(address, waitForDeploymentSeconds); - log.info( - "{} deployed at bounceable address {} ({})", - contract.getName(), - address.toBounceable(), - address.toRaw()); + public void showTxLiteClient(String address) { + ResultLastBlock resultLastBlock = LiteClientParser.parseLast(liteClient.executeLast()); + // log.info("resultLastBlock {}", resultLastBlock); + + for (int i = 0; i < 8; i++) { + + try { + ResultLastBlock blockId = resultLastBlock; + + if (i != 0) { + resultLastBlock.setSeqno(resultLastBlock.getSeqno().add(BigInteger.ONE)); + blockId = + LiteClientParser.parseBySeqno( + liteClient.executeBySeqno( + resultLastBlock.getWc(), + resultLastBlock.getShard(), + resultLastBlock.getSeqno())); + } + + log.info("resultLastBlock {}", blockId); + + List result = + liteClient.getAccountTransactionsFromBlockAndAllShards(blockId, address); + + if (!result.isEmpty()) { + org.ton.java.liteclient.api.block.Transaction.printTxHeader(""); + for (org.ton.java.liteclient.api.block.Transaction tx : result) { + tx.printTransactionFees(); + } + org.ton.java.liteclient.api.block.Transaction.printTxFooter(); + + org.ton.java.liteclient.api.block.MessageFees.printMessageFeesHeader(); + for (org.ton.java.liteclient.api.block.Transaction tx : result) { + tx.printAllMessages(false); + } + org.ton.java.liteclient.api.block.MessageFees.printMessageFeesFooter(); + // break; + } + } catch (Exception e) { + // asdf + } } - } } - } - - /** custom contract does not have conventional deploy methods */ - private void deployCustomContract(StateInit stateInit, int waitForDeploymentSeconds) - throws InterruptedException { - String contractName = - isNull(customContractAsResource) ? customContractPath : customContractAsResource; - Address address = stateInit.getAddress(); - log.info("Deploying {} on {}...", address.toRaw(), network); - if (network != Network.EMULATOR) { - ExtMessageInfo result; - - if (waitForDeploymentSeconds != 0) { - - if (network == Network.MAINNET) { - String nonBounceableAddress = address.toNonBounceable(); - - log.info( - "Waiting {}s for toncoins to be deposited to non-bounceable address {}", - waitForDeploymentSeconds, - nonBounceableAddress); - tonlib.waitForBalanceChange(address, waitForDeploymentSeconds); - log.info( - "Sending external message to non-bounceable address {} with deploy instructions...", - nonBounceableAddress); - Message msg = - MsgUtils.createExternalMessage( - address, - stateInit, - isNull(customContractBodyCell) ? stateInit.getData() : customContractDataCell); - result = tonlib.sendRawMessage(msg.toCell().toBase64()); - assert result.getError().getCode() != 0; - tonlib.waitForDeployment(address, waitForDeploymentSeconds); - log.info( - "{} deployed at bounceable address {} ({})", - contractName, - address.toBounceable(), - address.toRaw()); - } else if (network == Network.TESTNET) { - String nonBounceableAddress = address.toNonBounceableTestnet(); - log.info( - "Topping up non-bounceable {} with {} toncoin from TestnetFaucet", - nonBounceableAddress, - Utils.formatNanoValue(initialDeployTopUpAmount)); - BigInteger newBalance = - TestnetFaucet.topUpContract(tonlib, address, initialDeployTopUpAmount); - log.info( - "Topped up ({}) successfully, new balance {}", - nonBounceableAddress, - Utils.formatNanoValue(newBalance)); - log.info( - "Sending external message to non-bounceable address {} with deploy instructions...", - nonBounceableAddress); - Message msg = - MsgUtils.createExternalMessage( - address, - stateInit, - isNull(customContractBodyCell) ? stateInit.getData() : customContractDataCell); - result = tonlib.sendRawMessage(msg.toCell().toBase64()); - if (result.getError().getCode() != 0) { - throw new Error( - "Cannot send external message to non-bounceable address " - + nonBounceableAddress - + ". Error: " - + result.getError().getMessage()); - } - - tonlib.waitForDeployment(address, waitForDeploymentSeconds); - log.info( - "{} deployed at bounceable address {} ({})", - contractName, - address.toBounceableTestnet(), - address.toRaw()); - } else { // myLocalTon - String nonBounceableAddress = address.toNonBounceable(); - // top up first - BigInteger newBalance = topUpFromMyLocalTonFaucet(address); - log.info( - "Topped up ({}) successfully, new balance {}", - nonBounceableAddress, - Utils.formatNanoValue(newBalance)); - // deploy smc - Message msg = - MsgUtils.createExternalMessage( - address, - stateInit, - isNull(customContractBodyCell) ? stateInit.getData() : customContractDataCell); - result = tonlib.sendRawMessage(msg.toCell().toBase64()); - if (result.getError().getCode() != 0) { - throw new Error( - "Cannot send external message to non-bounceable address " - + nonBounceableAddress - + ". Error: " - + result.getError().getMessage()); - } - - tonlib.waitForDeployment(address, waitForDeploymentSeconds); - log.info( - "{} deployed at bounceable address {} ({})", - contractName, - address.toBounceable(), - address.toRaw()); + + public void showTxLiteClient() { + + List result = + liteClient.getAllTransactionsFromLatestBlockAndAllShards(); + + org.ton.java.liteclient.api.block.Transaction.printTxHeader(""); + for (org.ton.java.liteclient.api.block.Transaction tx : result) { + tx.printTransactionFees(); } - } + org.ton.java.liteclient.api.block.Transaction.printTxFooter(); + + org.ton.java.liteclient.api.block.MessageFees.printMessageFeesHeader(); + for (org.ton.java.liteclient.api.block.Transaction tx : result) { + tx.printAllMessages(false); + } + org.ton.java.liteclient.api.block.MessageFees.printMessageFeesFooter(); } - } + } diff --git a/blockchain/src/test/java/org/ton/java/BlockchainTest.java b/blockchain/src/test/java/org/ton/java/BlockchainTest.java index 3772fa7f..0acd2938 100644 --- a/blockchain/src/test/java/org/ton/java/BlockchainTest.java +++ b/blockchain/src/test/java/org/ton/java/BlockchainTest.java @@ -1,9 +1,6 @@ package org.ton.java; -import static org.assertj.core.api.Assertions.assertThat; - import com.iwebpp.crypto.TweetNaclFast; -import java.math.BigInteger; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; @@ -15,405 +12,520 @@ import org.ton.java.emulator.tx.TxVerbosityLevel; import org.ton.java.smartcontract.faucet.TestnetFaucet; import org.ton.java.smartcontract.types.WalletV3Config; -import org.ton.java.smartcontract.utils.MsgUtils; import org.ton.java.smartcontract.wallet.v3.WalletV3R2; import org.ton.java.smartcontract.wallet.v5.WalletV5; import org.ton.java.tlb.types.Message; -import org.ton.java.tonlib.types.VerbosityLevel; import org.ton.java.utils.Utils; +import java.math.BigInteger; + +import static org.assertj.core.api.Assertions.assertThat; + @Slf4j @RunWith(JUnit4.class) public class BlockchainTest { - Address dummyAddress = Address.of("EQAyjRKDnEpTBNfRHqYdnzGEQjdY4KG3gxgqiG3DpDY46u8G"); - - @Test - public void testDeployV3R2ContractOnEmulator() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); - Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testDeployV5ContractOnEmulator() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV5 wallet = - WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); - Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testDeployV5ContractOnMyLocalTon() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV5 wallet = - WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); - Blockchain blockchain = - Blockchain.builder() - .network(Network.MY_LOCAL_TON) - .myLocalTonInstallationPath("G:/Git_Projects/MyLocalTon/myLocalTon") - .contract(wallet) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testDeployV5ContractOnTestnet() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV5 wallet = - WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); - Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testDeployCustomContractContractOnEmulator() { - - Blockchain blockchain = - Blockchain.builder() - .network(Network.EMULATOR) - .customContractAsResource("simple.tolk") - .customContractDataCell( - CellBuilder.beginCell() - .storeUint(0, 32) // seqno - .storeInt( - Utils.getRandomInt(), - 32) // unique integer, to make contract address random each time - .endCell()) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testDeployCustomContractContractOnTestnet() { - - Blockchain blockchain = - Blockchain.builder() - .network(Network.TESTNET) - .customContractAsResource("simple.fc") - .customContractDataCell( + Address dummyAddress = Address.of("EQAyjRKDnEpTBNfRHqYdnzGEQjdY4KG3gxgqiG3DpDY46u8G"); + + @Test + public void testDeployV3R2ContractOnEmulator() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testDeployV5ContractOnEmulator() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV5 wallet = + WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testDeployV5ContractOnMyLocalTon() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV5 wallet = + WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); + Blockchain blockchain = + Blockchain.builder() + .network(Network.MY_LOCAL_TON) + .myLocalTonInstallationPath("G:/Git_Projects/MyLocalTon/myLocalTon") + .contract(wallet) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testDeployV5ContractOnTestnet() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV5 wallet = + WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); + Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testDeployCustomContractContractOnEmulator() { + + Blockchain blockchain = + Blockchain.builder() + .network(Network.EMULATOR) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .storeInt( + Utils.getRandomInt(), + 32) // unique integer, to make contract address random each time + .endCell()) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testDeployCustomContractContractOnTestnet() { + + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customContractAsResource("simple.fc") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testDeployCustomContractContractWithBodyOnTestnet() { + + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customContractAsResource("simple.fc") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(1, 32) // seqno + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .customContractBodyCell( + CellBuilder.beginCell() + .storeUint(1, 32) // seqno + .endCell()) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + } + + @Test + public void testGetMethodsV3R2ContractOnEmulator() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + GetterResult result = blockchain.runGetMethod("seqno"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + log.info("pubKey {}", blockchain.runGetPublicKey()); + } + + @Test + public void testGetMethodsV5ContractOnEmulator() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV5 wallet = + WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + GetterResult result = blockchain.runGetMethod("seqno"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + log.info("pubKey {}", blockchain.runGetPublicKey()); + log.info("subWalletId {}", blockchain.runGetSubWalletId()); + } + + @Test + public void testGetMethodsCustomContractOnEmulator() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.EMULATOR) + .customContractAsResource("simple.fc") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .build(); + GetterResult result = blockchain.runGetMethod("unique"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + } + + @Test + public void testGetMethodsV3R2ContractOnTestnet() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); + Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + GetterResult result = blockchain.runGetMethod("seqno"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + log.info("pubKey {}", blockchain.runGetPublicKey()); + } + + @Test + public void testGetMethodsV5ContractOnTestnet() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV5 wallet = + WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); + Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + GetterResult result = blockchain.runGetMethod("seqno"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + log.info("pubKey {}", blockchain.runGetPublicKey()); + log.info("subWalletId {}", blockchain.runGetSubWalletId()); + } + + @Test + public void testGetMethodsCustomContractOnTestnet() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customContractAsResource("simple.fc") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + GetterResult result = blockchain.runGetMethod("unique"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + } + + @Test + public void testGetMethodsCustomContractOnTestnetTolk() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + GetterResult result = blockchain.runGetMethod("unique"); + log.info("result {}", result); + log.info("seqno {}", blockchain.runGetSeqNo()); + } + + @Test + public void testSendMessageV3R2ContractOnTestnet() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); + Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + + WalletV3Config configA = + WalletV3Config.builder() + .walletId(42) + .seqno(blockchain.runGetSeqNo().longValue()) + .destination(Address.of(TestnetFaucet.FAUCET_ADDRESS_RAW)) + .amount(Utils.toNano(0.05)) + .comment("ton4j-test") + .mode(3) + .build(); + + Message msg = wallet.prepareExternalMsg(configA); + + blockchain.sendExternal(msg); + } + + @Test + public void testSendMessageV3R2ContractOnTestnetCustomWorkchain() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42) + .wc(-1) // custom workchain + .build(); + Blockchain blockchain = Blockchain.builder().network(Network.TESTNET) + .initialDeployTopUpAmount(Utils.toNano(1)) + .contract(wallet) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + + log.info("seqno {}", blockchain.runGetSeqNo()); + + WalletV3Config configA = + WalletV3Config.builder() + .walletId(42) + .seqno(blockchain.runGetSeqNo().longValue()) + .destination(Address.of(TestnetFaucet.FAUCET_ADDRESS_RAW)) + .amount(Utils.toNano(0.1)) + .comment("ton4j-test-асдф") + .mode(3) + .build(); + + Message msg = wallet.prepareExternalMsg(configA); + + blockchain.sendExternal(msg, 15); + log.info("seqno {}", blockchain.runGetSeqNo()); + } + + @Test + public void testSendMessageV3R2ContractOnEmulator() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + + assertThat(blockchain.deploy(30)).isTrue(); + log.info("seqno {}", blockchain.runGetSeqNo()); + + WalletV3Config configA = + WalletV3Config.builder() + .walletId(42) + .seqno(blockchain.runGetSeqNo().longValue()) + .destination(Address.of(TestnetFaucet.FAUCET_ADDRESS_RAW)) + .amount(Utils.toNano(0.05)) + .comment("ton4j-test") + .mode(3) + .build(); + + Message msg = wallet.prepareExternalMsg(configA); + + blockchain.sendExternal(msg); + log.info("seqno {}", blockchain.runGetSeqNo()); + } + + @Test + public void testSendMessageV3R2ContractOnEmulatorCustomWorkchain() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42) + .wc(-1) // custom workchain + .build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + log.info("seqno {}", blockchain.runGetSeqNo()); + + WalletV3Config configA = + WalletV3Config.builder() + .walletId(42) + .seqno(blockchain.runGetSeqNo().longValue()) + .destination(Address.of(TestnetFaucet.FAUCET_ADDRESS_RAW)) + .amount(Utils.toNano(0.05)) + .comment("ton4j-test") + .mode(3) + .build(); + + Message msg = wallet.prepareExternalMsg(configA); + + blockchain.sendExternal(msg); + log.info("seqno {}", blockchain.runGetSeqNo()); + } + + @Test(expected = Error.class) + public void testSendMessageV3R2ContractOnEmulatorErrorNoMethod() { + TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); + WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); + Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); + assertThat(blockchain.deploy(30)).isTrue(); + blockchain.runGetMethod("unique"); + } + + @Test + public void testSendMessageCustomContractOnTestnetTolk() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) + .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) + // .tonlibVerbosityLevel(VerbosityLevel.DEBUG) + .build(); + + assertThat(blockchain.deploy(30)).isTrue(); + + blockchain.runGetMethod("unique"); + System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + + Cell bodyCell = CellBuilder.beginCell() - .storeUint(0, 32) // seqno - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testDeployCustomContractContractWithBodyOnTestnet() { - - Blockchain blockchain = - Blockchain.builder() - .network(Network.TESTNET) - .customContractAsResource("simple.fc") - .customContractDataCell( + .storeUint(1, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell, 15); + } + + @Test + public void testSendMessageCustomContractOnTestnetTolkWithCustomTestnetGlobalConfig() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customGlobalConfigPath("g:/testnet-global.config.json") + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .printTxBlockData(false) + .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) + .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) + // .tonlibVerbosityLevel(VerbosityLevel.DEBUG) + .build(); + + assertThat(blockchain.deploy(30)).isTrue(); + + blockchain.runGetMethod("unique"); + System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + + Cell bodyCell = CellBuilder.beginCell() - .storeUint(1, 32) // seqno - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .customContractBodyCell( + .storeUint(1, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell, 15); + } + + @Test + public void testSendMessageCustomContractOnTestnetTolkWithCustomWorkchain() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.TESTNET) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .customContractWorkchain(-1) + .build(); + + assertThat(blockchain.deploy(30)).isTrue(); + + blockchain.runGetMethod("unique"); + System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + + Cell bodyCell = CellBuilder.beginCell() - .storeUint(1, 32) // seqno - .endCell()) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - } - - @Test - public void testGetMethodsV3R2ContractOnEmulator() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); - Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - GetterResult result = blockchain.runGetMethod("seqno"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - log.info("pubKey {}", blockchain.runGetPublicKey()); - } - - @Test - public void testGetMethodsV5ContractOnEmulator() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV5 wallet = - WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); - Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - GetterResult result = blockchain.runGetMethod("seqno"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - log.info("pubKey {}", blockchain.runGetPublicKey()); - log.info("subWalletId {}", blockchain.runGetSubWalletId()); - } - - @Test - public void testGetMethodsCustomContractOnEmulator() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.EMULATOR) - .customContractAsResource("simple.fc") - .customContractDataCell( + .storeUint(1, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell, 15); + } + + @Test + public void testSendMessageCustomContractOnEmulatorTolk() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.EMULATOR) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(1, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + // .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) + // .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) + .build(); + + assertThat(blockchain.deploy(30)).isTrue(); + + blockchain.runGetMethod("unique"); + System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + + Cell bodyCell = CellBuilder.beginCell() - .storeUint(0, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .build(); - GetterResult result = blockchain.runGetMethod("unique"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - } - - @Test - public void testGetMethodsV3R2ContractOnTestnet() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); - Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - GetterResult result = blockchain.runGetMethod("seqno"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - log.info("pubKey {}", blockchain.runGetPublicKey()); - } - - @Test - public void testGetMethodsV5ContractOnTestnet() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV5 wallet = - WalletV5.builder().keyPair(keyPair).walletId(42).isSigAuthAllowed(true).build(); - Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - GetterResult result = blockchain.runGetMethod("seqno"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - log.info("pubKey {}", blockchain.runGetPublicKey()); - log.info("subWalletId {}", blockchain.runGetSubWalletId()); - } - - @Test - public void testGetMethodsCustomContractOnTestnet() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.TESTNET) - .customContractAsResource("simple.fc") - .customContractDataCell( + .storeUint(1, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell); + System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + } + + @Test + public void testSendMessagesChainCustomContractOnEmulatorTolk() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.EMULATOR) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + // .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) + // .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + blockchain.runGetMethod("unique"); + BigInteger currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("returned seqno %s\n", currentSeqno); + + Cell bodyCell = CellBuilder.beginCell() - .storeUint(0, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - GetterResult result = blockchain.runGetMethod("unique"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - } - - @Test - public void testGetMethodsCustomContractOnTestnetTolk() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.TESTNET) - .customContractAsResource("simple.tolk") - .customContractDataCell( - CellBuilder.beginCell() - .storeUint(0, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - GetterResult result = blockchain.runGetMethod("unique"); - log.info("result {}", result); - log.info("seqno {}", blockchain.runGetSeqNo()); - } - - @Test - public void testSendMessageV3R2ContractOnTestnet() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); - Blockchain blockchain = Blockchain.builder().network(Network.TESTNET).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - - WalletV3Config configA = - WalletV3Config.builder() - .walletId(42) - .seqno(blockchain.runGetSeqNo().longValue()) - .destination(Address.of(TestnetFaucet.FAUCET_ADDRESS_RAW)) - .amount(Utils.toNano(0.05)) - .comment("ton4j-test") - .mode(3) - .build(); - - Message msg = wallet.prepareExternalMsg(configA); - - blockchain.sendExternal(msg); - } - - @Test - public void testSendMessageV3R2ContractOnEmulator() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); - Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - - WalletV3Config configA = - WalletV3Config.builder() - .walletId(42) - .seqno(blockchain.runGetSeqNo().longValue()) - .destination(Address.of(TestnetFaucet.FAUCET_ADDRESS_RAW)) - .amount(Utils.toNano(0.05)) - .comment("ton4j-test") - .mode(3) - .build(); - - Message msg = wallet.prepareExternalMsg(configA); - - SendExternalResult result = blockchain.sendExternal(msg); - log.info("result {}", result); - } - - @Test(expected = Error.class) - public void testSendMessageV3R2ContractOnEmulatorErrorNoMethod() { - TweetNaclFast.Signature.KeyPair keyPair = Utils.generateSignatureKeyPair(); - WalletV3R2 wallet = WalletV3R2.builder().keyPair(keyPair).walletId(42).build(); - Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); - assertThat(blockchain.deploy(30)).isTrue(); - blockchain.runGetMethod("unique"); - } - - @Test - public void testSendMessageCustomContractOnTestnetTolk() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.TESTNET) - .customContractAsResource("simple.tolk") - .customContractDataCell( + .storeUint(0, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell); + + currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("returned seqno %s\n", currentSeqno); + + bodyCell = CellBuilder.beginCell() - .storeUint(0, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) - .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) -// .tonlibVerbosityLevel(VerbosityLevel.DEBUG) - .build(); - - assertThat(blockchain.deploy(30)).isTrue(); - - blockchain.runGetMethod("unique"); - System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); - - Cell bodyCell = - CellBuilder.beginCell() - .storeUint(1, 32) // seqno - .endCell(); - - blockchain.sendExternal(bodyCell); - //wait till delivered - System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); - } - - @Test - public void testSendMessageCustomContractOnEmulatorTolk() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.EMULATOR) - .customContractAsResource("simple.tolk") - .customContractDataCell( + .storeUint(1, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell); + currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("returned seqno %s\n", currentSeqno); + } + + @Test + public void testSendMessagesChainCustomContractOnEmulatorFunc() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.EMULATOR) + .customContractAsResource("simple.fc") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + blockchain.runGetMethod("unique"); + BigInteger currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("returned seqno %s\n", currentSeqno); + + Cell bodyCell = CellBuilder.beginCell() - .storeUint(1, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - // .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) - // .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) - .build(); - - assertThat(blockchain.deploy(30)).isTrue(); - - blockchain.runGetMethod("unique"); - System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); - - Cell bodyCell = - CellBuilder.beginCell() - .storeUint(1, 32) // seqno - .endCell(); - - blockchain.sendExternal(bodyCell); - System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); - } - - @Test - public void testSendMessagesChainCustomContractOnEmulatorTolk() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.EMULATOR) - .customContractAsResource("simple.tolk") - .customContractDataCell( + .storeUint(0, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell); + + currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("returned seqno %s\n", currentSeqno); + + bodyCell = CellBuilder.beginCell() - .storeUint(0, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - // .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) - // .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - blockchain.runGetMethod("unique"); - BigInteger currentSeqno = blockchain.runGetSeqNo(); - System.out.printf("current seqno %s\n", currentSeqno); - - Cell bodyCell = - CellBuilder.beginCell() - .storeUint(0, 32) // seqno - .endCell(); - - blockchain.sendExternal(bodyCell); - - currentSeqno = blockchain.runGetSeqNo(); - System.out.printf("current seqno %s\n", currentSeqno); - - bodyCell = - CellBuilder.beginCell() - .storeUint(1, 32) // seqno - .endCell(); - - blockchain.sendExternal(bodyCell); - currentSeqno = blockchain.runGetSeqNo(); - System.out.printf("current seqno %s\n", currentSeqno); - } - - - @Test - public void testSendMessagesChainCustomContractOnEmulatorFunc() { - Blockchain blockchain = - Blockchain.builder() - .network(Network.EMULATOR) - .customContractAsResource("simple.fc") - .customContractDataCell( - CellBuilder.beginCell() - .storeUint(0, 32) - .storeInt(Utils.getRandomInt(), 32) - .endCell()) - .build(); - assertThat(blockchain.deploy(30)).isTrue(); - blockchain.runGetMethod("unique"); - BigInteger currentSeqno = blockchain.runGetSeqNo(); - System.out.printf("current seqno %s\n", currentSeqno); - - Cell bodyCell = - CellBuilder.beginCell() - .storeUint(0, 32) // seqno - .endCell(); - - blockchain.sendExternal(bodyCell); - - currentSeqno = blockchain.runGetSeqNo(); - System.out.printf("current seqno %s\n", currentSeqno); - - bodyCell = - CellBuilder.beginCell() - .storeUint(1, 32) // seqno - .endCell(); - - blockchain.sendExternal(bodyCell); - currentSeqno = blockchain.runGetSeqNo(); - System.out.printf("current seqno %s\n", currentSeqno); - } + .storeUint(1, 32) // seqno + .endCell(); + + blockchain.sendExternal(bodyCell, 15); + currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("returned seqno %s\n", currentSeqno); + } } diff --git a/cell/src/main/java/org/ton/java/cell/Cell.java b/cell/src/main/java/org/ton/java/cell/Cell.java index 0f3bf5ec..07b82800 100644 --- a/cell/src/main/java/org/ton/java/cell/Cell.java +++ b/cell/src/main/java/org/ton/java/cell/Cell.java @@ -1,1030 +1,1035 @@ package org.ton.java.cell; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; -import static org.ton.java.cell.CellType.ORDINARY; -import static org.ton.java.cell.CellType.UNKNOWN; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; +import org.ton.java.bitstring.BitString; +import org.ton.java.utils.Utils; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; -import org.ton.java.bitstring.BitString; -import org.ton.java.utils.Utils; -/** Implements Cell class, where BitString having elements of Boolean type. */ +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; +import static org.ton.java.cell.CellType.ORDINARY; +import static org.ton.java.cell.CellType.UNKNOWN; + +/** + * Implements Cell class, where BitString having elements of Boolean type. + */ @Slf4j public class Cell { - BitString bits; - List refs = new ArrayList<>(); - private CellType type; - public int index; - public boolean exotic; - public LevelMask levelMask; - private byte[] hashes = new byte[0]; - private int[] depthLevels = new int[0]; - - public BitString getBits() { - return bits; - } - - public boolean isExotic() { - return exotic; - } - - public List getRefs() { - return new ArrayList<>(refs); - } - - public byte[] getHashes() { - return hashes; - } - - public int[] getDepthLevels() { - return depthLevels; - } - - @Override - public int hashCode() { - return new BigInteger(this.getHash()).intValue(); - } - - @Override - public boolean equals(Object o) { - if (o instanceof Cell) { - return Arrays.equals(this.getHash(), ((Cell) o).getHash()); - } else { - return false; - } - } - - public Cell() { - this.bits = new BitString(); - this.exotic = false; - this.type = ORDINARY; - this.levelMask = new LevelMask(0); - } - - public Cell(int bitSize) { - this.bits = new BitString(bitSize); - this.exotic = false; - this.type = ORDINARY; - this.levelMask = resolveMask(); - } - - public Cell(BitString bits, List refs) { - this.bits = new BitString(bits.getLength()); - this.bits.writeBitString(bits.clone()); - this.refs = new ArrayList<>(refs); - this.exotic = false; - this.type = ORDINARY; - this.levelMask = new LevelMask(0); - } - - public Cell(BitString bits, List refs, int cellType) { - this.bits = new BitString(bits.getLength()); - this.bits.writeBitString(bits.clone()); - this.refs = new ArrayList<>(refs); - this.exotic = false; - this.type = toCellType(cellType); - this.levelMask = new LevelMask(0); - } - - public Cell(BitString bits, int bitSize, List refs, boolean exotic, LevelMask levelMask) { - this.bits = new BitString(bitSize); - this.bits.writeBitString(bits); - this.refs = new ArrayList<>(refs); - this.exotic = exotic; - this.type = ORDINARY; - this.levelMask = levelMask; - } - - public Cell(BitString bits, int bitSize, List refs, boolean exotic, CellType cellType) { - this.bits = new BitString(bitSize); - this.bits.writeBitString(bits); - this.refs = new ArrayList<>(refs); - this.exotic = exotic; - this.type = cellType; - this.levelMask = resolveMask(); - } - - public Cell(BitString bits, int bitSize, List refs, CellType cellType) { - this.bits = new BitString(bitSize); - this.bits.writeBitString(bits); - this.refs = new ArrayList<>(refs); - this.type = cellType; - this.levelMask = resolveMask(); - } - - public static CellType toCellType(int cellType) { - switch (cellType) { - case -1: - return CellType.ORDINARY; - case 1: - return CellType.PRUNED_BRANCH; - case 2: - return CellType.LIBRARY; - case 3: - return CellType.MERKLE_PROOF; - case 4: - return CellType.MERKLE_UPDATE; - default: - return CellType.UNKNOWN; - } - } - - public LevelMask resolveMask() { - // taken from pytoniq-core - if (this.type == ORDINARY) { - // Ordinary Cell level = max(Cell refs) - int mask = 0; - for (Cell r : refs) { - mask |= r.getMaxLevel(); - } - return new LevelMask(mask); - } else if (this.type == CellType.PRUNED_BRANCH) { - if (!refs.isEmpty()) { - throw new Error("Pruned branch must not have refs"); - } - BitString bs = bits.clone(); - bs.readUint8(); - - return new LevelMask(bs.readUint8().intValue()); - } else if (this.type == CellType.MERKLE_PROOF) { - // merkle proof cell has exactly one ref - return new LevelMask(refs.get(0).levelMask.getMask() >> 1); - } else if (this.type == CellType.MERKLE_UPDATE) { - // merkle update cell has exactly 2 refs - return new LevelMask(refs.get(0).levelMask.getMask() | refs.get(1).levelMask.getMask() >> 1); - } else if (this.type == CellType.LIBRARY) { - return new LevelMask(0); - } else { - throw new Error("Unknown cell type " + this.type); - } - } - - public void calculateHashes() { - - int totalHashCount = levelMask.getHashIndex() + 1; - hashes = new byte[32 * totalHashCount]; - depthLevels = new int[totalHashCount]; - - int hashCount = totalHashCount; - if (type == CellType.PRUNED_BRANCH) { - hashCount = 1; - } - - int hashIndexOffset = totalHashCount - hashCount; - int hashIndex = 0; - int level = levelMask.getLevel(); - - int off; - - for (int li = 0; li <= level; li++) { - if (!levelMask.isSignificant(li)) { - continue; - } - if (hashIndex < hashIndexOffset) { - hashIndex++; - continue; - } - - byte[] dsc = getDescriptors(levelMask.apply(li).getLevel()); - - byte[] hash = new byte[0]; - hash = Utils.concatBytes(hash, dsc); - - if (hashIndex == hashIndexOffset) { - if ((li != 0) && (type != CellType.PRUNED_BRANCH)) { - throw new Error("invalid cell"); - } + BitString bits; + List refs = new ArrayList<>(); + private CellType type; + public int index; + public boolean exotic; + public LevelMask levelMask; + private byte[] hashes = new byte[0]; + private int[] depthLevels = new int[0]; + + public BitString getBits() { + return bits; + } - byte[] data = getDataBytes(); - hash = Utils.concatBytes(hash, data); - } else { - if ((li == 0) && (type == CellType.PRUNED_BRANCH)) { - throw new Error("neither pruned nor 0"); - } - off = hashIndex - hashIndexOffset - 1; - byte[] partHash = new byte[32]; - System.arraycopy(hashes, off * 32, partHash, 0, (off + 1) * 32); - hash = Utils.concatBytes(hash, partHash); - } - - int depth = 0; - - for (Cell r : refs) { - int childDepth; - if ((type == CellType.MERKLE_PROOF) || (type == CellType.MERKLE_UPDATE)) { - childDepth = r.getDepth(li + 1); + public boolean isExotic() { + return exotic; + } + + public List getRefs() { + return new ArrayList<>(refs); + } + + public byte[] getHashes() { + return hashes; + } + + public int[] getDepthLevels() { + return depthLevels; + } + + @Override + public int hashCode() { + return new BigInteger(this.getHash()).intValue(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Cell) { + return Arrays.equals(this.getHash(), ((Cell) o).getHash()); } else { - childDepth = r.getDepth(li); + return false; } + } - hash = Utils.concatBytes(hash, Utils.intToByteArray(childDepth)); - if (childDepth > depth) { - depth = childDepth; - } - } - if (!refs.isEmpty()) { - depth++; - if (depth >= 1024) { - throw new Error("depth is more than max depth (1023)"); + public Cell() { + this.bits = new BitString(); + this.exotic = false; + this.type = ORDINARY; + this.levelMask = new LevelMask(0); + } + + public Cell(int bitSize) { + this.bits = new BitString(bitSize); + this.exotic = false; + this.type = ORDINARY; + this.levelMask = resolveMask(); + } + + public Cell(BitString bits, List refs) { + this.bits = new BitString(bits.getLength()); + this.bits.writeBitString(bits.clone()); + this.refs = new ArrayList<>(refs); + this.exotic = false; + this.type = ORDINARY; + this.levelMask = new LevelMask(0); + } + + public Cell(BitString bits, List refs, int cellType) { + this.bits = new BitString(bits.getLength()); + this.bits.writeBitString(bits.clone()); + this.refs = new ArrayList<>(refs); + this.exotic = false; + this.type = toCellType(cellType); + this.levelMask = new LevelMask(0); + } + + public Cell(BitString bits, int bitSize, List refs, boolean exotic, LevelMask levelMask) { + this.bits = new BitString(bitSize); + this.bits.writeBitString(bits); + this.refs = new ArrayList<>(refs); + this.exotic = exotic; + this.type = ORDINARY; + this.levelMask = levelMask; + } + + public Cell(BitString bits, int bitSize, List refs, boolean exotic, CellType cellType) { + this.bits = new BitString(bitSize); + this.bits.writeBitString(bits); + this.refs = new ArrayList<>(refs); + this.exotic = exotic; + this.type = cellType; + this.levelMask = resolveMask(); + } + + public Cell(BitString bits, int bitSize, List refs, CellType cellType) { + this.bits = new BitString(bitSize); + this.bits.writeBitString(bits); + this.refs = new ArrayList<>(refs); + this.type = cellType; + this.levelMask = resolveMask(); + } + + public static CellType toCellType(int cellType) { + switch (cellType) { + case -1: + return CellType.ORDINARY; + case 1: + return CellType.PRUNED_BRANCH; + case 2: + return CellType.LIBRARY; + case 3: + return CellType.MERKLE_PROOF; + case 4: + return CellType.MERKLE_UPDATE; + default: + return CellType.UNKNOWN; } - } + } - for (Cell r : refs) { - if ((type == CellType.MERKLE_PROOF) || (type == CellType.MERKLE_UPDATE)) { - hash = Utils.concatBytes(hash, r.getHash(li + 1)); + public LevelMask resolveMask() { + // taken from pytoniq-core + if (this.type == ORDINARY) { + // Ordinary Cell level = max(Cell refs) + int mask = 0; + for (Cell r : refs) { + mask |= r.getMaxLevel(); + } + return new LevelMask(mask); + } else if (this.type == CellType.PRUNED_BRANCH) { + if (!refs.isEmpty()) { + throw new Error("Pruned branch must not have refs"); + } + BitString bs = bits.clone(); + bs.readUint8(); + + return new LevelMask(bs.readUint8().intValue()); + } else if (this.type == CellType.MERKLE_PROOF) { + // merkle proof cell has exactly one ref + return new LevelMask(refs.get(0).levelMask.getMask() >> 1); + } else if (this.type == CellType.MERKLE_UPDATE) { + // merkle update cell has exactly 2 refs + return new LevelMask(refs.get(0).levelMask.getMask() | refs.get(1).levelMask.getMask() >> 1); + } else if (this.type == CellType.LIBRARY) { + return new LevelMask(0); } else { - hash = Utils.concatBytes(hash, r.getHash(li)); + throw new Error("Unknown cell type " + this.type); } - } - - off = hashIndex - hashIndexOffset; - depthLevels[off] = depth; - System.arraycopy(Utils.sha256AsArray(hash), 0, hashes, off * 32, 32); - hashIndex++; - } - } - - void setCellType(CellType pCellType) { - type = pCellType; - } - - void setExotic(boolean pExotic) { - exotic = pExotic; - } - - /** - * Converts BoC in hex string to Cell - * - * @param data hex string containing valid BoC - * @return Cell - */ - public static Cell fromBoc(String data) { - return fromBocMultiRoot(Utils.hexToSignedBytes(data)).get(0); - } - - /** - * Converts BoC in base64 string to Cell - * - * @param data base64 string containing valid BoC - * @return Cell - */ - public static Cell fromBocBase64(String data) { - return fromBocMultiRoot(Utils.base64ToSignedBytes(data)).get(0); - } - - public static Cell fromBoc(byte[] data) { - return fromBocMultiRoot(data).get(0); - } - - public static List fromBocMultiRoots(String data) { - return fromBocMultiRoot(Utils.hexToSignedBytes(data)); - } - - public static List fromBocMultiRoots(byte[] data) { - return fromBocMultiRoot(data); - } - - public String toString() { - return bits.toHex(); - } - - public int getBitLength() { - return bits.toBitString().length(); - } - - public Cell clone() { - Cell c = new Cell(); - c.bits = this.bits.clone(); - for (Cell refCell : this.refs) { - c.refs.add(refCell.clone()); - } - c.exotic = this.exotic; - c.type = this.type; - c.levelMask = this.levelMask.clone(); - c.hashes = Arrays.copyOf(this.hashes, this.hashes.length); - c.depthLevels = Arrays.copyOf(this.depthLevels, this.depthLevels.length); - return c; - } - - public void writeCell(Cell anotherCell) { - Cell cloned = anotherCell.clone(); - bits.writeBitString(cloned.bits); - refs.addAll(cloned.refs); - } - - public int getMaxRefs() { - return 4; - } - - public int getFreeRefs() { - return getMaxRefs() - refs.size(); - } - - public int getUsedRefs() { - return refs.size(); - } - - /** - * Loads bitString to Cell. Refs are not taken into account. - * - * @param hexBitString - bitString in hex - * @return Cell - */ - public static Cell fromHex(String hexBitString) { - try { - boolean incomplete = hexBitString.endsWith("_"); - - hexBitString = hexBitString.replaceAll("_", ""); - int[] b = Utils.hexToInts(hexBitString); - - BitString bs = new BitString(hexBitString.length() * 8); - bs.writeBytes(b); - - Boolean[] ba = bs.toBooleanArray(); - int i = ba.length - 1; - // drop last elements up to first `1`, if incomplete - while (incomplete && !ba[i]) { - ba = Arrays.copyOf(ba, ba.length - 1); - i--; - } - // if incomplete, drop the 1 as well - if (incomplete) { - ba = Arrays.copyOf(ba, ba.length - 1); - } - BitString bss = new BitString(ba.length); - bss.writeBitArray(ba); - - return CellBuilder.beginCell().storeBitString(bss).endCell(); - } catch (Exception e) { - throw new Error("Cannot convert hex BitString to Cell. Error " + e.getMessage()); - } - } - - static List fromBocMultiRoot(byte[] data) { - if (data.length < 10) { - throw new Error("Invalid boc"); - } - byte[] reachBocMagicPrefix = Utils.hexToSignedBytes("B5EE9C72"); - - UnsignedByteReader r = new UnsignedByteReader(data); - if (!Utils.compareBytes(reachBocMagicPrefix, r.readBytes(4))) { - throw new Error("Invalid boc magic header"); - } - - BocFlags bocFlags = parseBocFlags(r.readSignedByte()); - int dataSizeBytes = r.readByte(); // off_bytes:(## 8) { off_bytes <= 8 } - - long cellsNum = - Utils.dynInt(r.readSignedBytes(bocFlags.cellNumSizeBytes)); // cells:(##(size * 8)) - long rootsNum = - Utils.dynInt( - r.readSignedBytes(bocFlags.cellNumSizeBytes)); // roots:(##(size * 8)) { roots >= 1 } - - r.readBytes(bocFlags.cellNumSizeBytes); - long dataLen = Utils.dynInt(r.readSignedBytes(dataSizeBytes)); - - if (bocFlags.hasCrc32c) { - byte[] bocWithoutCrc = Arrays.copyOfRange(data, 0, data.length - 4); - byte[] crcInBoc = Arrays.copyOfRange(data, data.length - 4, data.length); - byte[] crc32 = Utils.getCRC32ChecksumAsBytesReversed(bocWithoutCrc); - if (!Utils.compareBytes(crc32, crcInBoc)) { - throw new Error("Crc32c hashsum mismatch"); - } - } - - int[] rootsIndex = new int[(int) rootsNum]; - for (int i = 0; i < rootsNum; i++) { - rootsIndex[i] = Utils.dynInt(r.readSignedBytes(bocFlags.cellNumSizeBytes)); - } - - if (bocFlags.hasCacheBits && !bocFlags.hasIndex) { - throw new Error("cache flag cant be set without index flag"); - } - - int[] index = new int[0]; - int j = 0; - if (bocFlags.hasIndex) { - index = new int[(int) cellsNum]; - byte[] idxData = r.readSignedBytes(cellsNum * dataSizeBytes); - - for (int i = 0; i < cellsNum; i++) { - int off = i * dataSizeBytes; - int val = Utils.dynInt(Arrays.copyOfRange(idxData, off, off + dataSizeBytes)); - if (bocFlags.hasCacheBits) { - val = val / 2; + } + + public void calculateHashes() { + + int totalHashCount = levelMask.getHashIndex() + 1; + hashes = new byte[32 * totalHashCount]; + depthLevels = new int[totalHashCount]; + + int hashCount = totalHashCount; + if (type == CellType.PRUNED_BRANCH) { + hashCount = 1; + } + + int hashIndexOffset = totalHashCount - hashCount; + int hashIndex = 0; + int level = levelMask.getLevel(); + + int off; + + for (int li = 0; li <= level; li++) { + if (!levelMask.isSignificant(li)) { + continue; + } + if (hashIndex < hashIndexOffset) { + hashIndex++; + continue; + } + + byte[] dsc = getDescriptors(levelMask.apply(li).getLevel()); + + byte[] hash = new byte[0]; + hash = Utils.concatBytes(hash, dsc); + + if (hashIndex == hashIndexOffset) { + if ((li != 0) && (type != CellType.PRUNED_BRANCH)) { + throw new Error("invalid cell"); + } + + byte[] data = getDataBytes(); + hash = Utils.concatBytes(hash, data); + } else { + if ((li == 0) && (type == CellType.PRUNED_BRANCH)) { + throw new Error("neither pruned nor 0"); + } + off = hashIndex - hashIndexOffset - 1; + byte[] partHash = new byte[32]; + System.arraycopy(hashes, off * 32, partHash, 0, (off + 1) * 32); + hash = Utils.concatBytes(hash, partHash); + } + + int depth = 0; + + for (Cell r : refs) { + int childDepth; + if ((type == CellType.MERKLE_PROOF) || (type == CellType.MERKLE_UPDATE)) { + childDepth = r.getDepth(li + 1); + } else { + childDepth = r.getDepth(li); + } + + hash = Utils.concatBytes(hash, Utils.intToByteArray(childDepth)); + if (childDepth > depth) { + depth = childDepth; + } + } + if (!refs.isEmpty()) { + depth++; + if (depth >= 1024) { + throw new Error("depth is more than max depth (1023)"); + } + } + + for (Cell r : refs) { + if ((type == CellType.MERKLE_PROOF) || (type == CellType.MERKLE_UPDATE)) { + hash = Utils.concatBytes(hash, r.getHash(li + 1)); + } else { + hash = Utils.concatBytes(hash, r.getHash(li)); + } + } + + off = hashIndex - hashIndexOffset; + depthLevels[off] = depth; + System.arraycopy(Utils.sha256AsArray(hash), 0, hashes, off * 32, 32); + hashIndex++; } - index[j++] = val; - } } - if (cellsNum > dataLen / 2) { - throw new Error("cells num looks malicious: data len " + data + ", cells " + cellsNum); + void setCellType(CellType pCellType) { + type = pCellType; } - byte[] payload = r.readBytes(dataLen); + void setExotic(boolean pExotic) { + exotic = pExotic; + } - return parseCells(rootsIndex, rootsNum, cellsNum, bocFlags.cellNumSizeBytes, payload, index); - } + /** + * Converts BoC in hex string to Cell + * + * @param data hex string containing valid BoC + * @return Cell + */ + public static Cell fromBoc(String data) { + return fromBocMultiRoot(Utils.hexToSignedBytes(data)).get(0); + } - private static List parseCells( - int[] rootsIndex, long rootsNum, long cellsNum, int refSzBytes, byte[] data, int[] index) { - Cell[] cells = new Cell[(int) cellsNum]; - for (int i = 0; i < cellsNum; i++) { - cells[i] = new Cell(); + /** + * Converts BoC in base64 string to Cell + * + * @param data base64 string containing valid BoC + * @return Cell + */ + public static Cell fromBocBase64(String data) { + return fromBocMultiRoot(Utils.base64ToSignedBytes(data)).get(0); } - int offset = 0; - for (int i = 0; i < cellsNum; i++) { - if ((data.length - offset) < 2) { - throw new Error("failed to parse cell header, corrupted data"); - } + public static Cell fromBoc(byte[] data) { + return fromBocMultiRoot(data).get(0); + } - if (nonNull(index) && (index.length != 0)) { - // if we have index, then set offset from it, it stores end of each cell - offset = 0; - if (i > 0) { - offset = index[i - 1]; + public static List fromBocMultiRoots(String data) { + return fromBocMultiRoot(Utils.hexToSignedBytes(data)); + } + + public static List fromBocMultiRoots(byte[] data) { + return fromBocMultiRoot(data); + } + + public String toString() { + return bits.toHex(); + } + + public int getBitLength() { + return bits.toBitString().length(); + } + + public Cell clone() { + Cell c = new Cell(); + c.bits = this.bits.clone(); + for (Cell refCell : this.refs) { + c.refs.add(refCell.clone()); } - } - - int flags = data[offset]; - int refsNum = flags & 0b111; - boolean special = (flags & 0b1000) != 0; - boolean withHashes = (flags & 0b10000) != 0; - LevelMask levelMask = new LevelMask(flags >> 5); - - if (refsNum > 4) { - throw new Error("too many refs in cell"); - } - - int ln = data[offset + 1] & 0xFF; - int oneMore = ln % 2; - int sz = (ln / 2 + oneMore); - - offset += 2; - if ((data.length - offset) < sz) { - throw new Error("failed to parse cell payload, corrupted data"); - } - - if (withHashes) { - int maskBits = (int) Math.ceil(Math.log(levelMask.mask + 1) / Math.log(2)); - int hashesNum = maskBits + 1; - offset += hashesNum * 32 + hashesNum * 2; - } - byte[] payload = Arrays.copyOfRange(data, offset, offset + sz); - - offset += sz; - if ((data.length - offset) < (refsNum * refSzBytes)) { - throw new Error("failed to parse cell refs, corrupted data"); - } - - int[] refsIndex = new int[refsNum]; - int x = 0; - for (int j = 0; j < refsNum; j++) { - byte[] t = Arrays.copyOfRange(data, offset, offset + refSzBytes); - refsIndex[x++] = Utils.dynInt(t); - - offset += refSzBytes; - } - - Cell[] refs = new Cell[refsIndex.length]; - - for (int y = 0; y < refsIndex.length; y++) { - if (i == refsIndex[y]) { - throw new Error("recursive reference of cells"); + c.exotic = this.exotic; + c.type = this.type; + c.levelMask = this.levelMask.clone(); + c.hashes = Arrays.copyOf(this.hashes, this.hashes.length); + c.depthLevels = Arrays.copyOf(this.depthLevels, this.depthLevels.length); + return c; + } + + public void writeCell(Cell anotherCell) { + Cell cloned = anotherCell.clone(); + bits.writeBitString(cloned.bits); + refs.addAll(cloned.refs); + } + + public int getMaxRefs() { + return 4; + } + + public int getFreeRefs() { + return getMaxRefs() - refs.size(); + } + + public int getUsedRefs() { + return refs.size(); + } + + /** + * Loads bitString to Cell. Refs are not taken into account. + * + * @param hexBitString - bitString in hex + * @return Cell + */ + public static Cell fromHex(String hexBitString) { + try { + boolean incomplete = hexBitString.endsWith("_"); + + hexBitString = hexBitString.replaceAll("_", ""); + int[] b = Utils.hexToInts(hexBitString); + + BitString bs = new BitString(hexBitString.length() * 8); + bs.writeBytes(b); + + Boolean[] ba = bs.toBooleanArray(); + int i = ba.length - 1; + // drop last elements up to first `1`, if incomplete + while (incomplete && !ba[i]) { + ba = Arrays.copyOf(ba, ba.length - 1); + i--; + } + // if incomplete, drop the 1 as well + if (incomplete) { + ba = Arrays.copyOf(ba, ba.length - 1); + } + BitString bss = new BitString(ba.length); + bss.writeBitArray(ba); + + return CellBuilder.beginCell().storeBitString(bss).endCell(); + } catch (Exception e) { + throw new Error("Cannot convert hex BitString to Cell. Error " + e.getMessage()); } + } - if ((refsIndex[y] < i) && (isNull(index))) { - throw new Error("reference to index which is behind parent cell"); + static List fromBocMultiRoot(byte[] data) { + if (data.length < 10) { + throw new Error("Invalid boc"); } + byte[] reachBocMagicPrefix = Utils.hexToSignedBytes("B5EE9C72"); - if (refsIndex[y] >= cells.length) { - throw new Error("invalid index, out of scope"); + UnsignedByteReader r = new UnsignedByteReader(data); + if (!Utils.compareBytes(reachBocMagicPrefix, r.readBytes(4))) { + throw new Error("Invalid boc magic header"); } - refs[y] = cells[refsIndex[y]]; - } + BocFlags bocFlags = parseBocFlags(r.readSignedByte()); + int dataSizeBytes = r.readByte(); // off_bytes:(## 8) { off_bytes <= 8 } - int bitSz = ln * 4; + long cellsNum = + Utils.dynInt(r.readSignedBytes(bocFlags.cellNumSizeBytes)); // cells:(##(size * 8)) + long rootsNum = + Utils.dynInt( + r.readSignedBytes(bocFlags.cellNumSizeBytes)); // roots:(##(size * 8)) { roots >= 1 } - // if not full byte - if ((ln % 2) != 0) { - // find last bit of byte which indicates the end and cut it and next - for (int y = 0; y < 8; y++) { - if (((payload[payload.length - 1] >> y) & 1) == 1) { - bitSz += 3 - y; - break; - } + r.readBytes(bocFlags.cellNumSizeBytes); + long dataLen = Utils.dynInt(r.readSignedBytes(dataSizeBytes)); + + if (bocFlags.hasCrc32c) { + byte[] bocWithoutCrc = Arrays.copyOfRange(data, 0, data.length - 4); + byte[] crcInBoc = Arrays.copyOfRange(data, data.length - 4, data.length); + byte[] crc32 = Utils.getCRC32ChecksumAsBytesReversed(bocWithoutCrc); + if (!Utils.compareBytes(crc32, crcInBoc)) { + throw new Error("Crc32c hashsum mismatch"); + } } - } - - cells[i].bits = new BitString(payload, bitSz); - cells[i].refs = Arrays.asList(refs); - cells[i].exotic = special; // i = 264 / ddnew 0101 - cells[i].levelMask = levelMask; - - cells[i].type = cells[i].getCellType(); - } - - Cell[] roots = new Cell[rootsIndex.length]; - - for (int i = cells.length - 1; i >= 0; i--) { - cells[i].calculateHashes(); - } - - for (int i = 0; i < rootsIndex.length; i++) { - roots[i] = cells[rootsIndex[i]]; - } - - return Arrays.asList(roots); - } - - /** - * has_idx:(## 1) has_crc32c:(## 1) has_cache_bits:(## 1) flags:(## 2) { flags = 0 } size:(## 3) { - * size <= 4 } - * - * @param data raw data - * @return BocFlags - */ - static BocFlags parseBocFlags(byte data) { - BocFlags bocFlags = new BocFlags(); - bocFlags.hasIndex = (data & (1 << 7)) > 0; - bocFlags.hasCrc32c = (data & (1 << 6)) > 0; - bocFlags.hasCacheBits = (data & (1 << 5)) > 0; - bocFlags.cellNumSizeBytes = (data & 0b00000111); - return bocFlags; - } - - /** - * Recursively prints cell's content like Fift - * - * @return String - */ - public String print(String indent) { - String t = "x"; - if (this.type == CellType.MERKLE_PROOF) { - t = "p"; - } else if (this.type == CellType.MERKLE_UPDATE) { - t = "u"; - } else if (this.type == CellType.PRUNED_BRANCH) { - t = "P"; - } - StringBuilder s = new StringBuilder(indent + t + "{" + bits.toHex() + "}\n"); - if (nonNull(refs) && !refs.isEmpty()) { - for (Cell i : refs) { - if (nonNull(i)) { - s.append(i.print(indent + " ")); + + int[] rootsIndex = new int[(int) rootsNum]; + for (int i = 0; i < rootsNum; i++) { + rootsIndex[i] = Utils.dynInt(r.readSignedBytes(bocFlags.cellNumSizeBytes)); } - } - } - return s.toString(); - } - - public String print() { - return print(""); - } - - /** Saves BoC to file */ - public void toFile(String filename, boolean withCrc) { - byte[] boc = toBoc(withCrc); - try { - Files.write(Paths.get(filename), boc); - } catch (Exception e) { - log.error("Cannot write to file. Error: {} ", e.getMessage()); - } - } - - public void toFile(String filename) { - toFile(filename, true); - } - - public String toHex(boolean withCrc) { - return Utils.bytesToHex(toBoc(withCrc)); - } - - public String toHex(boolean withCrc, boolean withIndex) { - return Utils.bytesToHex(toBoc(withCrc, withIndex)); - } - - public String toHex(boolean withCrc, boolean withIndex, boolean withCacheBits) { - return Utils.bytesToHex(toBoc(withCrc, withIndex, withCacheBits)); - } - - public String toHex( - boolean withCrc, boolean withIndex, boolean withCacheBits, boolean withTopHash) { - return Utils.bytesToHex(toBoc(withCrc, withIndex, withCacheBits, withTopHash)); - } - - public String toHex( - boolean withCrc, - boolean withIndex, - boolean withCacheBits, - boolean withTopHash, - boolean withIntHashes) { - return Utils.bytesToHex(toBoc(withCrc, withIndex, withCacheBits, withTopHash, withIntHashes)); - } - - /** - * BoC to hex - * - * @return HexString - */ - public String toHex() { - return Utils.bytesToHex(toBoc(true)); - } - - public String bitStringToHex() { - return bits.toHex(); - } - - public String toBitString() { - return bits.toBitString(); - } - - public String toBase64() { - return Utils.bytesToBase64(toBoc(true)); - } - - public String toBase64UrlSafe() { - return Utils.bytesToBase64SafeUrl(toBoc(true)); - } - - public String toBase64(boolean withCrc) { - return Utils.bytesToBase64(toBoc(withCrc)); - } - - public byte[] hash() { - return getHash(); - } - - public byte[] getHash() { - return getHash(levelMask.getLevel()); - } - - public String getShortHash() { - String hashHex = Utils.bytesToHex(getHash(levelMask.getLevel())); - return hashHex.substring(0, 4) + ".." + hashHex.substring(hashHex.length() - 5, hashHex.length() - 1); - } - - public byte[] getHash(int lvl) { - int hashIndex = levelMask.apply(lvl).getHashIndex(); - if (type == CellType.PRUNED_BRANCH) { - int prunedHashIndex = levelMask.getHashIndex(); - if (hashIndex != prunedHashIndex) { - return Arrays.copyOfRange(getDataBytes(), 2 + (hashIndex * 32), 2 + ((hashIndex + 1) * 32)); - } - hashIndex = 0; - } - - return Utils.slice(hashes, hashIndex * 32, 32); - } - - public byte[] getRefsDescriptor(int lvl) { - byte[] d1 = new byte[1]; - d1[0] = (byte) (isNull(refs) ? 0 : refs.size() + ((exotic ? 1 : 0) * 8) + lvl * 32); - return d1; - } - - public byte[] getBitsDescriptor() { - int bitsLength = bits.getLength(); - byte d3 = (byte) ((bitsLength / 8) * 2); - if ((bitsLength % 8) != 0) { - d3++; - } - return new byte[] {d3}; - } - - public int getMaxLevel() { - // TODO level calculation differ for exotic cells - int maxLevel = 0; - for (Cell i : refs) { - if (i.getMaxLevel() > maxLevel) { - maxLevel = i.getMaxLevel(); - } - } - return maxLevel; - } - - public byte[] toBoc() { - return toBoc(true, false, false, false, false); - } - - public byte[] toBoc(boolean withCRC) { - return toBoc(withCRC, false, false, false, false); - } - - public byte[] toBoc(boolean withCRC, boolean withIdx) { - return toBoc(withCRC, withIdx, false, false, false); - } - - public byte[] toBoc(boolean withCRC, boolean withIdx, boolean withCacheBits) { - return toBoc(withCRC, withIdx, withCacheBits, false, false); - } - - public byte[] toBoc( - boolean withCRC, boolean withIdx, boolean withCacheBits, boolean withTopHash) { - return toBoc(withCRC, withIdx, withCacheBits, withTopHash, false); - } - - private byte[] internalToBoc( - List roots, - boolean hasCrc32c, - boolean hasIdx, - boolean hasCacheBits, - boolean hasTopHash, - boolean hasIntHashes) { - Pair, Map> sortedCellsAndIndex = flattenIndex(roots, hasTopHash); - List sortedCells = sortedCellsAndIndex.getLeft(); - Map index = sortedCellsAndIndex.getRight(); + if (bocFlags.hasCacheBits && !bocFlags.hasIndex) { + throw new Error("cache flag cant be set without index flag"); + } - int cellSizeBits = Utils.log2(sortedCells.size() + 1); - int cellSizeBytes = (int) Math.ceil((double) cellSizeBits / 8); + int[] index = new int[0]; + int j = 0; + if (bocFlags.hasIndex) { + index = new int[(int) cellsNum]; + byte[] idxData = r.readSignedBytes(cellsNum * dataSizeBytes); + + for (int i = 0; i < cellsNum; i++) { + int off = i * dataSizeBytes; + int val = Utils.dynInt(Arrays.copyOfRange(idxData, off, off + dataSizeBytes)); + if (bocFlags.hasCacheBits) { + val = val / 2; + } + index[j++] = val; + } + } - byte[] payload = new byte[0]; - for (IdxItem sortedCell : sortedCells) { - payload = - Utils.concatBytes( - payload, - sortedCell.getCell().serialize(cellSizeBytes, index, sortedCell.isWithHash())); - sortedCell.dataIndex = payload.length; - } + if (cellsNum > dataLen / 2) { + throw new Error("cells num looks malicious: data len " + data + ", cells " + cellsNum); + } - // bytes needed to store len of payload - int sizeBits = Utils.log2Ceil(payload.length + 1); - byte sizeBytes = (byte) ((sizeBits + 7) / 8); + byte[] payload = r.readBytes(dataLen); - // has_idx 1bit, hash_crc32 1bit, has_cache_bits 1bit, flags 2bit, size_bytes 3 bit - byte flagsByte = 0; - if (hasIdx) { - flagsByte |= 0b1_0_0_00_000; - } - if (hasCrc32c) { - flagsByte |= 0b0_1_0_00_000; - } - if (hasCacheBits) { - flagsByte |= 0b0_0_1_00_000; + return parseCells(rootsIndex, rootsNum, cellsNum, bocFlags.cellNumSizeBytes, payload, index); } - flagsByte |= cellSizeBytes; + private static List parseCells( + int[] rootsIndex, long rootsNum, long cellsNum, int refSzBytes, byte[] data, int[] index) { + Cell[] cells = new Cell[(int) cellsNum]; + for (int i = 0; i < cellsNum; i++) { + cells[i] = new Cell(); + } + + int offset = 0; + for (int i = 0; i < cellsNum; i++) { + if ((data.length - offset) < 2) { + throw new Error("failed to parse cell header, corrupted data"); + } + + if (nonNull(index) && (index.length != 0)) { + // if we have index, then set offset from it, it stores end of each cell + offset = 0; + if (i > 0) { + offset = index[i - 1]; + } + } - byte[] data = new byte[0]; + int flags = data[offset]; + int refsNum = flags & 0b111; + boolean special = (flags & 0b1000) != 0; + boolean withHashes = (flags & 0b10000) != 0; + LevelMask levelMask = new LevelMask(flags >> 5); - byte[] bocMagic = new byte[] {(byte) 0xB5, (byte) 0xEE, (byte) 0x9C, 0x72}; + if (refsNum > 4) { + throw new Error("too many refs in cell"); + } - data = Utils.concatBytes(data, bocMagic); + int ln = data[offset + 1] & 0xFF; + int oneMore = ln % 2; + int sz = (ln / 2 + oneMore); - data = Utils.concatBytes(data, Utils.byteToBytes(flagsByte)); + offset += 2; + if ((data.length - offset) < sz) { + throw new Error("failed to parse cell payload, corrupted data"); + } - // bytes needed to store size - data = Utils.concatBytes(data, Utils.byteToBytes(sizeBytes)); + if (withHashes) { + int maskBits = (int) Math.ceil(Math.log(levelMask.mask + 1) / Math.log(2)); + int hashesNum = maskBits + 1; + offset += hashesNum * 32 + hashesNum * 2; + } + byte[] payload = Arrays.copyOfRange(data, offset, offset + sz); - // cells num - data = - Utils.concatBytes( - data, Utils.dynamicIntBytes(BigInteger.valueOf(sortedCells.size()), cellSizeBytes)); + offset += sz; + if ((data.length - offset) < (refsNum * refSzBytes)) { + throw new Error("failed to parse cell refs, corrupted data"); + } + + int[] refsIndex = new int[refsNum]; + int x = 0; + for (int j = 0; j < refsNum; j++) { + byte[] t = Arrays.copyOfRange(data, offset, offset + refSzBytes); + refsIndex[x++] = Utils.dynInt(t); - // roots num - data = Utils.concatBytes(data, Utils.dynamicIntBytes(BigInteger.ONE, cellSizeBytes)); + offset += refSzBytes; + } - // complete BOCs = 0 - data = Utils.concatBytes(data, Utils.dynamicIntBytes(BigInteger.ZERO, cellSizeBytes)); + Cell[] refs = new Cell[refsIndex.length]; - // len of data - data = - Utils.concatBytes( - data, Utils.dynamicIntBytes(BigInteger.valueOf(payload.length), sizeBytes)); + for (int y = 0; y < refsIndex.length; y++) { + if (i == refsIndex[y]) { + throw new Error("recursive reference of cells"); + } - // for roots TODO - for (Cell c : roots) { - data = - Utils.concatBytes( - data, - Utils.dynamicIntBytes(index.get(Utils.bytesToHex(c.getHash())).index, cellSizeBytes)); + if ((refsIndex[y] < i) && (isNull(index))) { + throw new Error("reference to index which is behind parent cell"); + } + + if (refsIndex[y] >= cells.length) { + throw new Error("invalid index, out of scope"); + } + + refs[y] = cells[refsIndex[y]]; + } + + int bitSz = ln * 4; + + // if not full byte + if ((ln % 2) != 0) { + // find last bit of byte which indicates the end and cut it and next + for (int y = 0; y < 8; y++) { + if (((payload[payload.length - 1] >> y) & 1) == 1) { + bitSz += 3 - y; + break; + } + } + } + + cells[i].bits = new BitString(payload, bitSz); + cells[i].refs = Arrays.asList(refs); + cells[i].exotic = special; // i = 264 / ddnew 0101 + cells[i].levelMask = levelMask; + + cells[i].type = cells[i].getCellType(); + } + + Cell[] roots = new Cell[rootsIndex.length]; + + for (int i = cells.length - 1; i >= 0; i--) { + cells[i].calculateHashes(); + } + + for (int i = 0; i < rootsIndex.length; i++) { + roots[i] = cells[rootsIndex[i]]; + } + + return Arrays.asList(roots); } - if (hasIdx) { - for (IdxItem sc : sortedCells) { - long idx = sc.dataIndex; - if (hasCacheBits) { - idx *= 2; - if (sc.repeats > 0) { - // cache cells which has refs - idx++; - } + /** + * has_idx:(## 1) has_crc32c:(## 1) has_cache_bits:(## 1) flags:(## 2) { flags = 0 } size:(## 3) { + * size <= 4 } + * + * @param data raw data + * @return BocFlags + */ + static BocFlags parseBocFlags(byte data) { + BocFlags bocFlags = new BocFlags(); + bocFlags.hasIndex = (data & (1 << 7)) > 0; + bocFlags.hasCrc32c = (data & (1 << 6)) > 0; + bocFlags.hasCacheBits = (data & (1 << 5)) > 0; + bocFlags.cellNumSizeBytes = (data & 0b00000111); + return bocFlags; + } + + /** + * Recursively prints cell's content like Fift + * + * @return String + */ + public String print(String indent) { + String t = "x"; + if (this.type == CellType.MERKLE_PROOF) { + t = "p"; + } else if (this.type == CellType.MERKLE_UPDATE) { + t = "u"; + } else if (this.type == CellType.PRUNED_BRANCH) { + t = "P"; + } + StringBuilder s = new StringBuilder(indent + t + "{" + bits.toHex() + "}\n"); + if (nonNull(refs) && !refs.isEmpty()) { + for (Cell i : refs) { + if (nonNull(i)) { + s.append(i.print(indent + " ")); + } + } } - data = Utils.concatBytes(data, Utils.dynamicIntBytes(BigInteger.valueOf(idx), sizeBytes)); - } + return s.toString(); } - data = Utils.appendByteArray(data, payload); + public String print() { + return print(""); + } - if (hasCrc32c) { - data = Utils.appendByteArray(data, Utils.getCRC32ChecksumAsBytesReversed(data)); + /** + * Saves BoC to file + */ + public void toFile(String filename, boolean withCrc) { + byte[] boc = toBoc(withCrc); + try { + Files.write(Paths.get(filename), boc); + } catch (Exception e) { + log.error("Cannot write to file. Error: {} ", e.getMessage()); + } } - return data; - } + public void toFile(String filename) { + toFile(filename, true); + } - public byte[] toBoc( - boolean hasCrc32c, - boolean hasIdx, - boolean hasCacheBits, - boolean hasTopHash, - boolean hasIntHashes) { + public String toHex(boolean withCrc) { + return Utils.bytesToHex(toBoc(withCrc)); + } - return internalToBoc( - Collections.singletonList(this), hasCrc32c, hasIdx, hasCacheBits, hasTopHash, hasIntHashes); - } + public String toHex(boolean withCrc, boolean withIndex) { + return Utils.bytesToHex(toBoc(withCrc, withIndex)); + } - public byte[] toBocMultiRoot( - List roots, - boolean hasCrc32c, - boolean hasIdx, - boolean hasCacheBits, - boolean hasTopHash, - boolean hasIntHashes) { - return internalToBoc(roots, hasCrc32c, hasIdx, hasCacheBits, hasTopHash, hasIntHashes); - } + public String toHex(boolean withCrc, boolean withIndex, boolean withCacheBits) { + return Utils.bytesToHex(toBoc(withCrc, withIndex, withCacheBits)); + } - /** reworked in order to coincide with tonutils-go */ - private Pair, Map> flattenIndex( - List roots, boolean hasTopHash) { - Map index = new HashMap<>(); + public String toHex( + boolean withCrc, boolean withIndex, boolean withCacheBits, boolean withTopHash) { + return Utils.bytesToHex(toBoc(withCrc, withIndex, withCacheBits, withTopHash)); + } - BigInteger idx = BigInteger.ZERO; + public String toHex( + boolean withCrc, + boolean withIndex, + boolean withCacheBits, + boolean withTopHash, + boolean withIntHashes) { + return Utils.bytesToHex(toBoc(withCrc, withIndex, withCacheBits, withTopHash, withIntHashes)); + } - while (roots.size() > 0) { - List next = new ArrayList<>(roots.size() * 4); - for (Cell p : roots) { - String hash = Utils.bytesToHex(p.getHash()); + /** + * BoC to hex + * + * @return HexString + */ + public String toHex() { + return Utils.bytesToHex(toBoc(true)); + } - IdxItem v = index.get(hash); - if (nonNull(v)) { - v.repeats++; - continue; - } + public String bitStringToHex() { + return bits.toHex(); + } + + public String toBitString() { + return bits.toBitString(); + } + + public String toBase64() { + return Utils.bytesToBase64(toBoc(true)); + } + + public String toBase64UrlSafe() { + return Utils.bytesToBase64SafeUrl(toBoc(true)); + } + + public String toBase64(boolean withCrc) { + return Utils.bytesToBase64(toBoc(withCrc)); + } + + public byte[] hash() { + return getHash(); + } + + public byte[] getHash() { + return getHash(levelMask.getLevel()); + } + + public String getShortHash() { + String hashHex = Utils.bytesToHex(getHash(levelMask.getLevel())); + return hashHex.substring(0, 4) + + ".." + + hashHex.substring(hashHex.length() - 5, hashHex.length() - 1); + } - index.put(hash, IdxItem.builder().cell(p).index(idx).withHash(hasTopHash).build()); - idx = idx.add(BigInteger.ONE); + public byte[] getHash(int lvl) { + int hashIndex = levelMask.apply(lvl).getHashIndex(); + if (type == CellType.PRUNED_BRANCH) { + int prunedHashIndex = levelMask.getHashIndex(); + if (hashIndex != prunedHashIndex) { + return Arrays.copyOfRange(getDataBytes(), 2 + (hashIndex * 32), 2 + ((hashIndex + 1) * 32)); + } + hashIndex = 0; + } - next.addAll(p.getRefs()); - } - roots = next; + return Utils.slice(hashes, hashIndex * 32, 32); } - List idxSlice = new ArrayList<>(index.size()); - for (Map.Entry entry : index.entrySet()) { - String key = entry.getKey(); - IdxItem value = entry.getValue(); - idxSlice.add(value); + public byte[] getRefsDescriptor(int lvl) { + byte[] d1 = new byte[1]; + d1[0] = (byte) (isNull(refs) ? 0 : refs.size() + ((exotic ? 1 : 0) * 8) + lvl * 32); + return d1; } - idxSlice.sort(Comparator.comparing(lhs -> lhs.index)); // todo review + public byte[] getBitsDescriptor() { + int bitsLength = bits.getLength(); + byte d3 = (byte) ((bitsLength / 8) * 2); + if ((bitsLength % 8) != 0) { + d3++; + } + return new byte[]{d3}; + } - boolean verifyOrder = true; - while (verifyOrder) { - verifyOrder = false; - for (IdxItem id : idxSlice) { - for (Cell ref : id.getCell().getRefs()) { - IdxItem idRef = index.get(Utils.bytesToHex(ref.getHash())); - if (idRef.index.compareTo(id.index) < 0) { - idRef.index = idx; - idx = idx.add(BigInteger.ONE); // idx++ - verifyOrder = true; - } + public int getMaxLevel() { + // TODO level calculation differ for exotic cells + int maxLevel = 0; + for (Cell i : refs) { + if (i.getMaxLevel() > maxLevel) { + maxLevel = i.getMaxLevel(); + } } - } + return maxLevel; } - idxSlice.sort(Comparator.comparing(lhs -> lhs.index)); - for (int i = 0; i < idxSlice.size(); i++) { - idxSlice.get(i).index = BigInteger.valueOf(i); + public byte[] toBoc() { + return toBoc(true, false, false, false, false); } - return Pair.of(idxSlice, index); - } + public byte[] toBoc(boolean withCRC) { + return toBoc(withCRC, false, false, false, false); + } - private byte[] serialize(int refIndexSzBytes, Map index, boolean hasHash) { - byte[] body = - Utils.unsignedBytesToSigned(CellSlice.beginParse(this).loadSlice(this.bits.getLength())); + public byte[] toBoc(boolean withCRC, boolean withIdx) { + return toBoc(withCRC, withIdx, false, false, false); + } - int unusedBits = 8 - (bits.getLength() % 8); + public byte[] toBoc(boolean withCRC, boolean withIdx, boolean withCacheBits) { + return toBoc(withCRC, withIdx, withCacheBits, false, false); + } - if (unusedBits != 8) { - body[body.length - 1] += 1 << (unusedBits - 1); + public byte[] toBoc( + boolean withCRC, boolean withIdx, boolean withCacheBits, boolean withTopHash) { + return toBoc(withCRC, withIdx, withCacheBits, withTopHash, false); } - int refsLn = this.getRefs().size() * refIndexSzBytes; - int bufLn = 2 + body.length + refsLn; + private byte[] internalToBoc( + List roots, + boolean hasCrc32c, + boolean hasIdx, + boolean hasCacheBits, + boolean hasTopHash, + boolean hasIntHashes) { + Pair, Map> sortedCellsAndIndex = flattenIndex(roots, hasTopHash); + + List sortedCells = sortedCellsAndIndex.getLeft(); + Map index = sortedCellsAndIndex.getRight(); + + int cellSizeBits = Utils.log2(sortedCells.size() + 1); + int cellSizeBytes = (int) Math.ceil((double) cellSizeBits / 8); + + byte[] payload = new byte[0]; + for (IdxItem sortedCell : sortedCells) { + payload = + Utils.concatBytes( + payload, + sortedCell.getCell().serialize(cellSizeBytes, index, sortedCell.isWithHash())); + sortedCell.dataIndex = payload.length; + } + + // bytes needed to store len of payload + int sizeBits = Utils.log2Ceil(payload.length + 1); + byte sizeBytes = (byte) ((sizeBits + 7) / 8); + + // has_idx 1bit, hash_crc32 1bit, has_cache_bits 1bit, flags 2bit, size_bytes 3 bit + byte flagsByte = 0; + if (hasIdx) { + flagsByte |= 0b1_0_0_00_000; + } + if (hasCrc32c) { + flagsByte |= 0b0_1_0_00_000; + } + if (hasCacheBits) { + flagsByte |= 0b0_0_1_00_000; + } + + flagsByte |= cellSizeBytes; + + byte[] data = new byte[0]; + + byte[] bocMagic = new byte[]{(byte) 0xB5, (byte) 0xEE, (byte) 0x9C, 0x72}; + + data = Utils.concatBytes(data, bocMagic); + + data = Utils.concatBytes(data, Utils.byteToBytes(flagsByte)); + + // bytes needed to store size + data = Utils.concatBytes(data, Utils.byteToBytes(sizeBytes)); - byte[] data; + // cells num + data = + Utils.concatBytes( + data, Utils.dynamicIntBytes(BigInteger.valueOf(sortedCells.size()), cellSizeBytes)); - byte[] descriptors = getDescriptors(levelMask.getMask()); + // roots num + data = Utils.concatBytes(data, Utils.dynamicIntBytes(BigInteger.ONE, cellSizeBytes)); + + // complete BOCs = 0 + data = Utils.concatBytes(data, Utils.dynamicIntBytes(BigInteger.ZERO, cellSizeBytes)); + + // len of data + data = + Utils.concatBytes( + data, Utils.dynamicIntBytes(BigInteger.valueOf(payload.length), sizeBytes)); + + // for roots TODO + for (Cell c : roots) { + data = + Utils.concatBytes( + data, + Utils.dynamicIntBytes(index.get(Utils.bytesToHex(c.getHash())).index, cellSizeBytes)); + } + + if (hasIdx) { + for (IdxItem sc : sortedCells) { + long idx = sc.dataIndex; + if (hasCacheBits) { + idx *= 2; + if (sc.repeats > 0) { + // cache cells which has refs + idx++; + } + } + data = Utils.concatBytes(data, Utils.dynamicIntBytes(BigInteger.valueOf(idx), sizeBytes)); + } + } - data = Utils.concatBytes(descriptors, body); + data = Utils.appendByteArray(data, payload); - long refsOffset = bufLn - refsLn; - for (int i = 0; i < refs.size(); i++) { - String refIndex = Utils.bytesToHex(refs.get(i).getHash()); - byte[] src = Utils.dynamicIntBytes(index.get(refIndex).index, refIndexSzBytes); - data = Utils.concatBytes(data, src); + if (hasCrc32c) { + data = Utils.appendByteArray(data, Utils.getCRC32ChecksumAsBytesReversed(data)); + } + + return data; } - return data; - } + public byte[] toBoc( + boolean hasCrc32c, + boolean hasIdx, + boolean hasCacheBits, + boolean hasTopHash, + boolean hasIntHashes) { - private byte[] getDescriptors(int lvl) { - return Utils.concatBytes(getRefsDescriptor(lvl), getBitsDescriptor()); - } + return internalToBoc( + Collections.singletonList(this), hasCrc32c, hasIdx, hasCacheBits, hasTopHash, hasIntHashes); + } - private int getDepth(int lvlMask) { - int hashIndex = levelMask.apply(lvlMask).getHashIndex(); - if (type == CellType.PRUNED_BRANCH) { - int prunedHashIndex = levelMask.getHashIndex(); - if (hashIndex != prunedHashIndex) { - int off = 2 + 32 * prunedHashIndex + hashIndex * 2; - return Utils.bytesToIntX(Utils.slice(getDataBytes(), off, 2)); - } + public byte[] toBocMultiRoot( + List roots, + boolean hasCrc32c, + boolean hasIdx, + boolean hasCacheBits, + boolean hasTopHash, + boolean hasIntHashes) { + return internalToBoc(roots, hasCrc32c, hasIdx, hasCacheBits, hasTopHash, hasIntHashes); } - return depthLevels[hashIndex]; - } - private byte[] getDataBytes() { - if ((bits.getLength() % 8) > 0) { - byte[] a = bits.toBitString().getBytes(StandardCharsets.UTF_8); - int sz = a.length; - byte[] b = new byte[sz + 1]; + /** + * reworked in order to coincide with tonutils-go + */ + private Pair, Map> flattenIndex( + List roots, boolean hasTopHash) { + Map index = new HashMap<>(); + + BigInteger idx = BigInteger.ZERO; + + while (roots.size() > 0) { + List next = new ArrayList<>(roots.size() * 4); + for (Cell p : roots) { + String hash = Utils.bytesToHex(p.getHash()); + + IdxItem v = index.get(hash); + if (nonNull(v)) { + v.repeats++; + continue; + } + + index.put(hash, IdxItem.builder().cell(p).index(idx).withHash(hasTopHash).build()); + idx = idx.add(BigInteger.ONE); - System.arraycopy(a, 0, b, 0, sz); - b[sz] = (byte) '1'; + next.addAll(p.getRefs()); + } + roots = next; + } - int mod = b.length % 8; - if (mod > 0) { - b = Utils.rightPadBytes(b, b.length + (8 - mod), '0'); - } + List idxSlice = new ArrayList<>(index.size()); + for (Map.Entry entry : index.entrySet()) { + String key = entry.getKey(); + IdxItem value = entry.getValue(); + idxSlice.add(value); + } - return Utils.bitStringToByteArray(new String(b)); - } else { - return bits.toByteArray(); + idxSlice.sort(Comparator.comparing(lhs -> lhs.index)); // todo review + + boolean verifyOrder = true; + while (verifyOrder) { + verifyOrder = false; + for (IdxItem id : idxSlice) { + for (Cell ref : id.getCell().getRefs()) { + IdxItem idRef = index.get(Utils.bytesToHex(ref.getHash())); + if (idRef.index.compareTo(id.index) < 0) { + idRef.index = idx; + idx = idx.add(BigInteger.ONE); // idx++ + verifyOrder = true; + } + } + } + } + idxSlice.sort(Comparator.comparing(lhs -> lhs.index)); + + for (int i = 0; i < idxSlice.size(); i++) { + idxSlice.get(i).index = BigInteger.valueOf(i); + } + + return Pair.of(idxSlice, index); } - } - public static CellType getCellType(Cell c) { - return c.getCellType(); - } + private byte[] serialize(int refIndexSzBytes, Map index, boolean hasHash) { + byte[] body = + Utils.unsignedBytesToSigned(CellSlice.beginParse(this).loadSlice(this.bits.getLength())); + + int unusedBits = 8 - (bits.getLength() % 8); + + if (unusedBits != 8) { + body[body.length - 1] += 1 << (unusedBits - 1); + } + + int refsLn = this.getRefs().size() * refIndexSzBytes; + int bufLn = 2 + body.length + refsLn; + + byte[] data; + + byte[] descriptors = getDescriptors(levelMask.getMask()); - public CellType getCellType() { - if (!exotic) { - return ORDINARY; + data = Utils.concatBytes(descriptors, body); + + long refsOffset = bufLn - refsLn; + for (int i = 0; i < refs.size(); i++) { + String refIndex = Utils.bytesToHex(refs.get(i).getHash()); + byte[] src = Utils.dynamicIntBytes(index.get(refIndex).index, refIndexSzBytes); + data = Utils.concatBytes(data, src); + } + + return data; } - if (bits.getLength() < 8) { - return UNKNOWN; + private byte[] getDescriptors(int lvl) { + return Utils.concatBytes(getRefsDescriptor(lvl), getBitsDescriptor()); + } + + private int getDepth(int lvlMask) { + int hashIndex = levelMask.apply(lvlMask).getHashIndex(); + if (type == CellType.PRUNED_BRANCH) { + int prunedHashIndex = levelMask.getHashIndex(); + if (hashIndex != prunedHashIndex) { + int off = 2 + 32 * prunedHashIndex + hashIndex * 2; + return Utils.bytesToIntX(Utils.slice(getDataBytes(), off, 2)); + } + } + return depthLevels[hashIndex]; } - BitString clonedBits = bits.clone(); - CellType cellType = toCellType(clonedBits.readUint(8).intValue()); - switch (cellType) { - case PRUNED_BRANCH: - { - if (bits.getLength() >= 288) { - LevelMask msk = new LevelMask(clonedBits.readUint(8).intValue()); - int lvl = msk.getLevel(); - if ((lvl > 0) - && (lvl <= 3) - && (bits.getLength() >= 16 + (256 + 16) * msk.apply(lvl - 1).getHashIndex() + 1)) { - return CellType.PRUNED_BRANCH; + private byte[] getDataBytes() { + if ((bits.getLength() % 8) > 0) { + byte[] a = bits.toBitString().getBytes(StandardCharsets.UTF_8); + int sz = a.length; + byte[] b = new byte[sz + 1]; + + System.arraycopy(a, 0, b, 0, sz); + b[sz] = (byte) '1'; + + int mod = b.length % 8; + if (mod > 0) { + b = Utils.rightPadBytes(b, b.length + (8 - mod), '0'); } - } + + return Utils.bitStringToByteArray(new String(b)); + } else { + return bits.toByteArray(); } - case MERKLE_PROOF: - { - if ((refs.size() == 1) && (bits.getLength() == 280)) { - return CellType.MERKLE_PROOF; - } + } + + public static CellType getCellType(Cell c) { + return c.getCellType(); + } + + public CellType getCellType() { + if (!exotic) { + return ORDINARY; } - case MERKLE_UPDATE: - { - if ((refs.size() == 2) && (bits.getLength() == 552)) { - return CellType.MERKLE_UPDATE; - } + + if (bits.getLength() < 8) { + return UNKNOWN; } - case LIBRARY: - { - if (bits.getLength() == (8 + 256)) { - return CellType.LIBRARY; - } + + BitString clonedBits = bits.clone(); + CellType cellType = toCellType(clonedBits.readUint(8).intValue()); + switch (cellType) { + case PRUNED_BRANCH: { + if (bits.getLength() >= 288) { + LevelMask msk = new LevelMask(clonedBits.readUint(8).intValue()); + int lvl = msk.getLevel(); + if ((lvl > 0) + && (lvl <= 3) + && (bits.getLength() >= 16 + (256 + 16) * msk.apply(lvl - 1).getHashIndex() + 1)) { + return CellType.PRUNED_BRANCH; + } + } + } + case MERKLE_PROOF: { + if ((refs.size() == 1) && (bits.getLength() == 280)) { + return CellType.MERKLE_PROOF; + } + } + case MERKLE_UPDATE: { + if ((refs.size() == 2) && (bits.getLength() == 552)) { + return CellType.MERKLE_UPDATE; + } + } + case LIBRARY: { + if (bits.getLength() == (8 + 256)) { + return CellType.LIBRARY; + } + } } + return UNKNOWN; } - return UNKNOWN; - } } diff --git a/cell/src/main/java/org/ton/java/cell/CellBuilder.java b/cell/src/main/java/org/ton/java/cell/CellBuilder.java index 5f5fc5a5..1afd6989 100644 --- a/cell/src/main/java/org/ton/java/cell/CellBuilder.java +++ b/cell/src/main/java/org/ton/java/cell/CellBuilder.java @@ -1,15 +1,14 @@ package org.ton.java.cell; -import org.ton.java.address.Address; -import org.ton.java.bitstring.BitString; -import org.ton.java.utils.Utils; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.List; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; +import org.ton.java.address.Address; +import org.ton.java.bitstring.BitString; +import org.ton.java.utils.Utils; public class CellBuilder { @@ -221,6 +220,13 @@ public CellBuilder storeBitString(BitString bitString) { return this; } + public CellBuilder storeBitString(BitString bitString, int bits) { + checkBitsOverflow(bits); + BitString temp = bitString.readBits(267); + cell.bits.writeBitString(temp.clone()); + return this; + } + public CellBuilder storeBitStringUnsafe(BitString bitString) { cell.bits.writeBitString(bitString.clone()); return this; diff --git a/cell/src/main/java/org/ton/java/tlb/types/Block.java b/cell/src/main/java/org/ton/java/tlb/types/Block.java index ea05516e..3476802c 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/Block.java +++ b/cell/src/main/java/org/ton/java/tlb/types/Block.java @@ -1,7 +1,5 @@ package org.ton.java.tlb.types; -import java.util.ArrayList; -import java.util.List; import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -9,9 +7,10 @@ import org.ton.java.cell.CellBuilder; import org.ton.java.cell.CellSlice; +import java.util.ArrayList; +import java.util.List; + /** - * - * *
  * block#11ef55aa
  *   global_id:int32
@@ -25,118 +24,118 @@
 @Data
 @Slf4j
 public class Block {
-  long magic;
-  int globalId;
-  BlockInfo blockInfo;
-  ValueFlow valueFlow;
-  MerkleUpdate stateUpdate;
-  BlockExtra extra;
-
-  public Cell toCell() {
-    return CellBuilder.beginCell()
-        .storeUint(0x11ef55aa, 32)
-        .storeInt(globalId, 32)
-        .storeRef(blockInfo.toCell())
-        .storeRef(valueFlow.toCell())
-        .storeRef(stateUpdate.toCell())
-        .storeRef(extra.toCell())
-        .endCell();
-  }
-
-  public static Block deserialize(CellSlice cs) {
-
-    long magic = cs.loadUint(32).longValue();
-    assert (magic == 0x11ef55aaL)
-        : "Block: magic not equal to 0x11ef55aa, found 0x" + Long.toHexString(magic);
-
-    Block block =
-        Block.builder()
-            .magic(0x11ef55aaL)
-            .globalId(cs.loadInt(32).intValue())
-            .blockInfo(BlockInfo.deserialize(CellSlice.beginParse(cs.loadRef())))
-            .build();
-
-    block.setValueFlow(ValueFlow.deserialize(CellSlice.beginParse(cs.loadRef())));
-
-    MerkleUpdate merkleUpdate = MerkleUpdate.deserialize(CellSlice.beginParse(cs.loadRef()));
-    block.setStateUpdate(merkleUpdate);
-    block.setExtra(BlockExtra.deserialize(CellSlice.beginParse(cs.loadRef())));
-
-    return block;
-  }
-
-  public List getAllTransactions() {
-    List result = new ArrayList<>();
-    Block block = this;
-
-    List outMsgs = block.getExtra().getOutMsgDesc().getOutMessages();
-
-    for (OutMsg outMsg : outMsgs) {
-      if (outMsg instanceof OutMsgExt) {
-        result.add(((OutMsgExt) outMsg).getTransaction());
-      }
-      if (outMsg instanceof OutMsgImm) {
-        result.add(((OutMsgImm) outMsg).getTransaction());
-      }
-      if (outMsg instanceof OutMsgNew) {
-        result.add(((OutMsgNew) outMsg).getTransaction());
-      }
-      if (outMsg instanceof OutMsgNew) {
-        result.add(((OutMsgNew) outMsg).getTransaction());
-      }
+    long magic;
+    int globalId;
+    BlockInfo blockInfo;
+    ValueFlow valueFlow;
+    MerkleUpdate stateUpdate;
+    BlockExtra extra;
+
+    public Cell toCell() {
+        return CellBuilder.beginCell()
+                .storeUint(0x11ef55aa, 32)
+                .storeInt(globalId, 32)
+                .storeRef(blockInfo.toCell())
+                .storeRef(valueFlow.toCell())
+                .storeRef(stateUpdate.toCell())
+                .storeRef(extra.toCell())
+                .endCell();
     }
 
-    List inMsgs = block.getExtra().getInMsgDesc().getInMessages();
-    for (InMsg inMsg : inMsgs) {
-      if (inMsg instanceof InMsgImportExt) {
-        result.add(((InMsgImportExt) inMsg).getTransaction());
-      }
-      if (inMsg instanceof InMsgImportIhr) {
-        result.add(((InMsgImportIhr) inMsg).getTransaction());
-      }
-      if (inMsg instanceof InMsgImportImm) {
-        result.add(((InMsgImportImm) inMsg).getTransaction());
-      }
-      if (inMsg instanceof InMsgImportFin) {
-        result.add(((InMsgImportFin) inMsg).getTransaction());
-      }
-    }
-    return result;
-  }
-
-  public void printAllTransactions() {
-    List txs = getAllTransactions();
-    if (txs.isEmpty()) {
-      log.info("No transactions");
-      return;
+    public static Block deserialize(CellSlice cs) {
+
+        long magic = cs.loadUint(32).longValue();
+        assert (magic == 0x11ef55aaL)
+                : "Block: magic not equal to 0x11ef55aa, found 0x" + Long.toHexString(magic);
+
+        Block block =
+                Block.builder()
+                        .magic(0x11ef55aaL)
+                        .globalId(cs.loadInt(32).intValue())
+                        .blockInfo(BlockInfo.deserialize(CellSlice.beginParse(cs.loadRef())))
+                        .build();
+
+        block.setValueFlow(ValueFlow.deserialize(CellSlice.beginParse(cs.loadRef())));
+
+        MerkleUpdate merkleUpdate = MerkleUpdate.deserialize(CellSlice.beginParse(cs.loadRef()));
+        block.setStateUpdate(merkleUpdate);
+        block.setExtra(BlockExtra.deserialize(CellSlice.beginParse(cs.loadRef())));
+
+        return block;
     }
-    Transaction.printTxHeader("");
-    for (Transaction tx : txs) {
-      tx.printTransactionFees();
+
+    public List getAllTransactions() {
+        List result = new ArrayList<>();
+        Block block = this;
+
+        List outMsgs = block.getExtra().getOutMsgDesc().getOutMessages();
+
+        for (OutMsg outMsg : outMsgs) {
+            if (outMsg instanceof OutMsgExt) {
+                result.add(((OutMsgExt) outMsg).getTransaction());
+            }
+            if (outMsg instanceof OutMsgImm) {
+                result.add(((OutMsgImm) outMsg).getTransaction());
+            }
+            if (outMsg instanceof OutMsgNew) {
+                result.add(((OutMsgNew) outMsg).getTransaction());
+            }
+            if (outMsg instanceof OutMsgNew) {
+                result.add(((OutMsgNew) outMsg).getTransaction());
+            }
+        }
+
+        List inMsgs = block.getExtra().getInMsgDesc().getInMessages();
+        for (InMsg inMsg : inMsgs) {
+            if (inMsg instanceof InMsgImportExt) {
+                result.add(((InMsgImportExt) inMsg).getTransaction());
+            }
+            if (inMsg instanceof InMsgImportIhr) {
+                result.add(((InMsgImportIhr) inMsg).getTransaction());
+            }
+            if (inMsg instanceof InMsgImportImm) {
+                result.add(((InMsgImportImm) inMsg).getTransaction());
+            }
+            if (inMsg instanceof InMsgImportFin) {
+                result.add(((InMsgImportFin) inMsg).getTransaction());
+            }
+        }
+        return result;
     }
-    Transaction.printTxFooter();
-  }
-
-  public List getAllMessageFees() {
-    List txs = getAllTransactions();
-    List msgFees = new ArrayList<>();
-    for (Transaction tx : txs) {
-      msgFees.addAll(tx.getAllMessageFees());
+
+    public void printAllTransactions() {
+        List txs = getAllTransactions();
+        if (txs.isEmpty()) {
+            log.info("No transactions");
+            return;
+        }
+        Transaction.printTxHeader("");
+        for (Transaction tx : txs) {
+            tx.printTransactionInfo();
+        }
+        Transaction.printTxFooter();
     }
 
-    return msgFees;
-  }
+    public List getAllMessageFees() {
+        List txs = getAllTransactions();
+        List msgFees = new ArrayList<>();
+        for (Transaction tx : txs) {
+            msgFees.addAll(tx.getAllMessagePrintInfo());
+        }
 
-  public void printAllMessages() {
-    List msgFees = getAllMessageFees();
-    if (msgFees.isEmpty()) {
-      log.info("No messages");
-      return;
+        return msgFees;
     }
-    MessageFees.printMessageFeesHeader();
-    for (MessageFees msgFee : msgFees) {
-      msgFee.printMessageFees();
+
+    public void printAllMessages() {
+        List msgFees = getAllMessageFees();
+        if (msgFees.isEmpty()) {
+            log.info("No messages");
+            return;
+        }
+        MessagePrintInfo.printMessageInfoHeader();
+        for (MessagePrintInfo msgFee : msgFees) {
+            msgFee.printMessageInfo();
+        }
+        MessagePrintInfo.printMessageInfoFooter();
     }
-    MessageFees.printMessageFeesFooter();
-  }
 }
diff --git a/cell/src/main/java/org/ton/java/tlb/types/ComputePhaseVM.java b/cell/src/main/java/org/ton/java/tlb/types/ComputePhaseVM.java
index 59a691cb..f3ef34d8 100644
--- a/cell/src/main/java/org/ton/java/tlb/types/ComputePhaseVM.java
+++ b/cell/src/main/java/org/ton/java/tlb/types/ComputePhaseVM.java
@@ -10,13 +10,20 @@
 
 /**
  * 
- * tr_phase_compute_vm$1 success:Bool msg_state_used:Bool
- * account_activated:Bool gas_fees:Grams
+ * tr_phase_compute_vm$1
+ * success:Bool
+ * msg_state_used:Bool
+ * account_activated:Bool
+ * gas_fees:Grams
  * ^[ gas_used:(VarUInteger 7)
- * gas_limit:(VarUInteger 7) gas_credit:(Maybe (VarUInteger 3))
- * mode:int8 exit_code:int32 exit_arg:(Maybe int32)
+ * gas_limit:(VarUInteger 7)
+ * gas_credit:(Maybe (VarUInteger 3))
+ * mode:int8
+ * exit_code:int32
+ * exit_arg:(Maybe int32)
  * vm_steps:uint32
- * vm_init_state_hash:bits256 vm_final_state_hash:bits256 ]
+ * vm_init_state_hash:bits256
+ * vm_final_state_hash:bits256 ]
  * = TrComputePhase;
  * 
*/ 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 deleted file mode 100644 index d9293ff3..00000000 --- a/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.ton.java.tlb.types; - -import static java.util.Objects.isNull; - -import java.math.BigInteger; -import lombok.Builder; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.ton.java.utils.Utils; - -@Builder -@Data -@Slf4j -public class MessageFees { - String direction; - String type; - String op; - BigInteger fwdFee; - BigInteger value; - BigInteger ihrFee; - BigInteger createdAt; - BigInteger createdLt; - BigInteger importFee; - String src; - String dst; - - public void printMessageFees() { - MessageFees msgFee = this; - - String str = - String.format( - "| %-7s| %-13s| %-9s| %-16s| %-16s| %-16s| %-16s| %-20s| %-15s| %-16s| %-14s |", - msgFee.getDirection(), - msgFee.getType(), - msgFee.getOp(), - isNull(msgFee.getValue()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getValue()), - isNull(msgFee.getFwdFee()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getFwdFee()), - isNull(msgFee.getIhrFee()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getIhrFee()), - isNull(msgFee.getImportFee()) - ? "N/A" - : Utils.formatNanoValueZero(msgFee.getImportFee()), - isNull(msgFee.getCreatedAt()) - ? "N/A" - : (msgFee.getCreatedAt().longValue() == 0) - ? "0" - : Utils.toUTC(msgFee.getCreatedAt().longValue()), - isNull(msgFee.getCreatedLt()) ? "N/A" : msgFee.getCreatedLt().toString(), - getSrc(), - getDst()); - log.info(str); - } - - public static void printMessageFeesHeader() { - String header = - "| in/out | type | op | value | fwdFee | ihrFee | importFee | timestamp | lt | src | dst |"; - log.info(""); - log.info("Messages"); - log.info( - "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); - log.info(header); - log.info( - "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); - } - - public static void printMessageFeesFooter() { - log.info( - "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); - } - - public String getSrc() { - if (StringUtils.isNotEmpty(src)) { - return src.substring(0, 7) + "..." + src.substring(src.length() - 6, src.length() - 1); - } else { - return "N/A"; - } - } - - public String getDst() { - 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/MessagePrintInfo.java b/cell/src/main/java/org/ton/java/tlb/types/MessagePrintInfo.java new file mode 100644 index 00000000..ee7cf246 --- /dev/null +++ b/cell/src/main/java/org/ton/java/tlb/types/MessagePrintInfo.java @@ -0,0 +1,88 @@ +package org.ton.java.tlb.types; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ton.java.utils.Utils; + +import java.math.BigInteger; + +import static java.util.Objects.isNull; + +@Builder +@Data +@Slf4j +public class MessagePrintInfo { + String direction; + String type; + String op; + BigInteger fwdFee; + BigInteger value; + BigInteger ihrFee; + BigInteger createdAt; + BigInteger createdLt; + BigInteger importFee; + String src; + String dst; + String comment; + + private static String header = + "| in/out | type | op | value | fwdFee | ihrFee | importFee | timestamp | lt | src | dst | comment |"; + + public void printMessageInfo() { + MessagePrintInfo msgFee = this; + + String str = + String.format( + "| %-7s| %-13s| %-10s| %-16s| %-16s| %-16s| %-16s| %-20s| %-15s| %-16s| %-16s| %-27s|", + msgFee.getDirection(), + msgFee.getType(), + msgFee.getOp(), + isNull(msgFee.getValue()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getValue()), + isNull(msgFee.getFwdFee()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getFwdFee()), + isNull(msgFee.getIhrFee()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getIhrFee()), + isNull(msgFee.getImportFee()) + ? "N/A" + : Utils.formatNanoValueZero(msgFee.getImportFee()), + isNull(msgFee.getCreatedAt()) + ? "N/A" + : (msgFee.getCreatedAt().longValue() == 0) + ? "0" + : Utils.toUTC(msgFee.getCreatedAt().longValue()), + isNull(msgFee.getCreatedLt()) ? "N/A" : msgFee.getCreatedLt().toString(), + getShortSourceAddress(), + getShortDestinationAddress(), + msgFee.getComment() + ); + log.info(str); + } + + public static void printMessageInfoHeader() { + log.info(""); + log.info("Messages"); + log.info(StringUtils.repeat("-", header.length())); + log.info(header); + log.info(StringUtils.repeat("-", header.length())); + } + + public static void printMessageInfoFooter() { + log.info(StringUtils.repeat("-", header.length())); + } + + public String getShortSourceAddress() { + if (StringUtils.isNotEmpty(src)) { + return src.substring(0, 7) + "..." + src.substring(src.length() - 6, src.length() - 1); + } else { + return "N/A"; + } + } + + public String getShortDestinationAddress() { + 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/StoragePhase.java b/cell/src/main/java/org/ton/java/tlb/types/StoragePhase.java index 98ae0f9d..7fab707b 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/StoragePhase.java +++ b/cell/src/main/java/org/ton/java/tlb/types/StoragePhase.java @@ -10,7 +10,8 @@ /** *
- * tr_phase_storage$_ storage_fees_collected:Grams
+ * tr_phase_storage$_
+ *   storage_fees_collected:Grams
  *   storage_fees_due:(Maybe Grams)
  *   status_change:AccStatusChange
  *   = TrStoragePhase;
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 2f4af6ca..5884d7cb 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
@@ -1,11 +1,5 @@
 package org.ton.java.tlb.types;
 
-import static java.util.Objects.isNull;
-import static java.util.Objects.nonNull;
-
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.List;
 import lombok.Builder;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
@@ -16,9 +10,14 @@
 import org.ton.java.cell.TonHashMapE;
 import org.ton.java.utils.Utils;
 
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Objects.isNull;
+import static java.util.Objects.nonNull;
+
 /**
- *
- *
  * 
  * transaction$0111
  *   account_addr:bits256
@@ -42,576 +41,756 @@
 @Data
 @Slf4j
 public class Transaction {
-  int magic;
-  BigInteger accountAddr;
-  BigInteger lt;
-  BigInteger prevTxHash;
-  BigInteger prevTxLt;
-  long now;
-  long outMsgCount;
-  AccountStates origStatus;
-  AccountStates endStatus;
-  TransactionIO inOut;
-  CurrencyCollection totalFees;
-  HashUpdate stateUpdate;
-  TransactionDescription description;
-
-  // not in scheme, but might be filled based on request data for flexibility
-  byte[] hash;
-
-  private String getMagic() {
-    return Long.toBinaryString(magic);
-  }
-
-  private String getAccountAddrShort() {
-    if (nonNull(accountAddr)) {
-      String str64 = StringUtils.leftPad(accountAddr.toString(16), 64, "0");
-      return str64.substring(0, 5)
-          + "..."
-          + str64.substring(str64.length() - 6, str64.length() - 1);
-    } else {
-      return "N/A";
-    }
-  }
-
-  private String getPrevTxHash() {
-    if (nonNull(accountAddr)) {
-      return prevTxHash.toString(16);
-    } else {
-      return "null";
-    }
-  }
-
-  public Cell toCell() {
-    CellBuilder c = CellBuilder.beginCell();
-    c.storeUint(0b0111, 4);
-    c.storeUint(accountAddr, 256);
-    c.storeUint(lt, 64);
-    c.storeUint(prevTxHash, 256);
-    c.storeUint(prevTxLt, 64);
-    c.storeUint(now, 32);
-    c.storeUint(outMsgCount, 15);
-    c.storeCell(serializeAccountState(origStatus));
-    c.storeCell(serializeAccountState(endStatus));
-    c.storeCell(totalFees.toCell());
-
-    c.storeRef(inOut.toCell());
-    c.storeRef(stateUpdate.toCell());
-    c.storeRef(description.toCell());
-
-    return c.endCell();
-  }
-
-  public static Transaction deserialize(CellSlice cs) {
-    long magic = cs.loadUint(4).intValue();
-    assert (magic == 0b0111)
-        : "Transaction: magic not equal to 0b0111, found 0b" + Long.toBinaryString(magic);
-
-    Transaction tx =
-        Transaction.builder()
-            .magic(0b0111)
-            .accountAddr(cs.loadUint(256))
-            .lt(cs.loadUint(64))
-            .prevTxHash(cs.loadUint(256))
-            .prevTxLt(cs.loadUint(64))
-            .now(cs.loadUint(32).longValue())
-            .outMsgCount(cs.loadUint(15).intValue())
-            .origStatus(deserializeAccountState(cs.loadUint(2).byteValue()))
-            .endStatus(deserializeAccountState(cs.loadUint(2).byteValueExact()))
-            .build();
-
-    CellSlice inOutMsgs = CellSlice.beginParse(cs.loadRef());
-    Message msg =
-        inOutMsgs.loadBit() ? Message.deserialize(CellSlice.beginParse(inOutMsgs.loadRef())) : null;
-    TonHashMapE out =
-        inOutMsgs.loadDictE(
-            15,
-            k -> k.readInt(15),
-            v -> Message.deserialize(CellSlice.beginParse(CellSlice.beginParse(v).loadRef())));
-
-    tx.setInOut(TransactionIO.builder().in(msg).out(out).build());
-
-    //        if (nonNull(tx.getInOut().getOut())) { // todo cleanup
-    //            for (Map.Entry entry : tx.getInOut().getOut().elements.entrySet())
-    // {
-    //                log.info("key " + entry.getKey() + ", value " + ((Message)
-    // entry.getValue()));
-    //            }
-    //        }
-
-    tx.setTotalFees(CurrencyCollection.deserialize(cs));
-    tx.setStateUpdate(HashUpdate.deserialize(CellSlice.beginParse(cs.loadRef())));
-    tx.setDescription(TransactionDescription.deserialize(CellSlice.beginParse(cs.loadRef())));
-
-    return tx;
-  }
-
-  public static Cell serializeAccountState(AccountStates state) {
-    switch (state) {
-      case UNINIT:
-        {
-          return CellBuilder.beginCell().storeUint(0, 2).endCell();
+    int magic;
+    BigInteger accountAddr;
+    BigInteger lt;
+    BigInteger prevTxHash;
+    BigInteger prevTxLt;
+    long now;
+    long outMsgCount;
+    AccountStates origStatus;
+    AccountStates endStatus;
+    TransactionIO inOut;
+    CurrencyCollection totalFees;
+    HashUpdate stateUpdate;
+    TransactionDescription description;
+
+    // not in scheme, but might be filled based on request data for flexibility
+    byte[] hash;
+
+    private String getMagic() {
+        return Long.toBinaryString(magic);
+    }
+
+    private static String txHeaderInfoWithBlockTop = "|                                                                                                                  |         Storage Phase        |                  Compute Phase               |                          Action Phase                      |                                                 |";
+    private static String txHeaderInfoWithBlock = "| txHash  | timestamp | lt             | op        | type         | valueIn        | valueOut       | totalFees    | fees         | dueFees       | success | gasFees       | vmSteps | exitCode | success | fordwardFees | actionFees   | actions | exitCode | account       | block                           |";
+
+    private static String txHeaderInfoWithoutBlockTop = "|                                                                                                                  |         Storage Phase        |                  Compute Phase               |                          Action Phase                      |               | ";
+    private static String txHeaderInfoWithoutBlock = "| txHash  | timestamp | lt             | op        | type         | valueIn        | valueOut       | totalFees    | fees         | dueFees       | success | gasFees       | vmSteps | exitCode | success | fordwardFees | actionFees   | actions | exitCode | account       |";
+
+    private String getAccountAddrShort() {
+        if (nonNull(accountAddr)) {
+            String str64 = StringUtils.leftPad(accountAddr.toString(16), 64, "0");
+            return str64.substring(0, 5)
+                    + "..."
+                    + str64.substring(str64.length() - 5);
+        } else {
+            return "N/A";
         }
-      case FROZEN:
-        {
-          return CellBuilder.beginCell().storeUint(1, 2).endCell();
+    }
+
+    private String getPrevTxHash() {
+        if (nonNull(accountAddr)) {
+            return prevTxHash.toString(16);
+        } else {
+            return "null";
         }
-      case ACTIVE:
-        {
-          return CellBuilder.beginCell().storeUint(2, 2).endCell();
+    }
+
+    public Cell toCell() {
+        CellBuilder c = CellBuilder.beginCell();
+        c.storeUint(0b0111, 4);
+        c.storeUint(accountAddr, 256);
+        c.storeUint(lt, 64);
+        c.storeUint(prevTxHash, 256);
+        c.storeUint(prevTxLt, 64);
+        c.storeUint(now, 32);
+        c.storeUint(outMsgCount, 15);
+        c.storeCell(serializeAccountState(origStatus));
+        c.storeCell(serializeAccountState(endStatus));
+        c.storeCell(totalFees.toCell());
+
+        c.storeRef(inOut.toCell());
+        c.storeRef(stateUpdate.toCell());
+        c.storeRef(description.toCell());
+
+        return c.endCell();
+    }
+
+    public static Transaction deserialize(CellSlice cs) {
+        long magic = cs.loadUint(4).intValue();
+        assert (magic == 0b0111)
+                : "Transaction: magic not equal to 0b0111, found 0b" + Long.toBinaryString(magic);
+
+        Transaction tx =
+                Transaction.builder()
+                        .magic(0b0111)
+                        .accountAddr(cs.loadUint(256))
+                        .lt(cs.loadUint(64))
+                        .prevTxHash(cs.loadUint(256))
+                        .prevTxLt(cs.loadUint(64))
+                        .now(cs.loadUint(32).longValue())
+                        .outMsgCount(cs.loadUint(15).intValue())
+                        .origStatus(deserializeAccountState(cs.loadUint(2).byteValue()))
+                        .endStatus(deserializeAccountState(cs.loadUint(2).byteValueExact()))
+                        .build();
+
+        CellSlice inOutMsgs = CellSlice.beginParse(cs.loadRef());
+        Message msg =
+                inOutMsgs.loadBit() ? Message.deserialize(CellSlice.beginParse(inOutMsgs.loadRef())) : null;
+        TonHashMapE out =
+                inOutMsgs.loadDictE(
+                        15,
+                        k -> k.readInt(15),
+                        v -> Message.deserialize(CellSlice.beginParse(CellSlice.beginParse(v).loadRef())));
+
+        tx.setInOut(TransactionIO.builder().in(msg).out(out).build());
+
+        tx.setTotalFees(CurrencyCollection.deserialize(cs));
+        tx.setStateUpdate(HashUpdate.deserialize(CellSlice.beginParse(cs.loadRef())));
+        tx.setDescription(TransactionDescription.deserialize(CellSlice.beginParse(cs.loadRef())));
+
+        return tx;
+    }
+
+    public static Cell serializeAccountState(AccountStates state) {
+        switch (state) {
+            case UNINIT: {
+                return CellBuilder.beginCell().storeUint(0, 2).endCell();
+            }
+            case FROZEN: {
+                return CellBuilder.beginCell().storeUint(1, 2).endCell();
+            }
+            case ACTIVE: {
+                return CellBuilder.beginCell().storeUint(2, 2).endCell();
+            }
+            case NON_EXIST: {
+                return CellBuilder.beginCell().storeUint(3, 2).endCell();
+            }
         }
-      case NON_EXIST:
-        {
-          return CellBuilder.beginCell().storeUint(3, 2).endCell();
+        return null;
+    }
+
+    public static AccountStates deserializeAccountState(byte state) {
+        switch (state) {
+            case 0: {
+                return AccountStates.UNINIT;
+            }
+            case 1: {
+                return AccountStates.FROZEN;
+            }
+            case 2: {
+                return AccountStates.ACTIVE;
+            }
+            case 3: {
+                return AccountStates.NON_EXIST;
+            }
         }
+        return null;
     }
-    return null;
-  }
 
-  public static AccountStates deserializeAccountState(byte state) {
-    switch (state) {
-      case 0:
-        {
-          return AccountStates.UNINIT;
+    public TransactionPrintInfo getTransactionPrintInfo() {
+        Transaction tx = this;
+
+        BigInteger totalFees = tx.getTotalFees().getCoins();
+
+        StoragePhase storagePhase = tx.getStoragePhase(tx.getDescription());
+        ComputePhaseVM computePhase = tx.getComputePhaseVm(tx.getDescription());
+        ActionPhase actionPhase = tx.getActionPhase(tx.getDescription());
+
+        BigInteger storageFeesCollected = nonNull(storagePhase) ? storagePhase.getStorageFeesCollected() : null;
+        BigInteger storageDueFees = nonNull(storagePhase) ? storagePhase.getStorageFeesDue() : null;
+        String storageStatus = nonNull(storagePhase) ? storagePhase.getStatusChange().getType() : "N/A";
+
+        BigInteger computeGasFees = nonNull(computePhase) ? computePhase.getGasFees() : null;
+        long computeVmSteps = nonNull(computePhase) ? computePhase.getDetails().getVMSteps() : 0;
+        String computeSuccess = nonNull(computePhase) ? computePhase.isSuccess() ? "yes" : "no" :
+                nonNull(tx.getComputePhaseSkipReason(tx.getDescription())) ? "skipped" : "N/A";
+
+        BigInteger actionTotalFwdFees = nonNull(actionPhase) ? actionPhase.getTotalFwdFees() : null;
+        BigInteger actionTotalActionFees = nonNull(actionPhase) ? actionPhase.getTotalActionFees() : null;
+        String actionSuccess = nonNull(actionPhase) ? actionPhase.isSuccess() ? "yes" : "no" : "N/A";
+        long actionResultCode = nonNull(actionPhase) ? actionPhase.getResultCode() : 0;
+
+        BigInteger inForwardFees = BigInteger.ZERO;
+        BigInteger valueIn = BigInteger.ZERO;
+        BigInteger valueOut = BigInteger.ZERO;
+        BigInteger op = null;
+        long exitCode = getExitCode(tx.getDescription());
+        long actionCode = getActionCode(tx.getDescription());
+        long totalActions = getTotalActions(tx.getDescription());
+        long now = tx.getNow();
+        BigInteger lt = tx.getLt();
+        long outMsgs = tx.getOutMsgCount();
+        String hash = "not available";
+
+        if (nonNull(tx.getInOut())) {
+            Message inMsg = tx.getInOut().getIn();
+            if (nonNull(inMsg)) {
+                Cell body = inMsg.getBody();
+                hash = "todome";
+
+                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();
+                }
+            }
+
+            for (Message outMsg : tx.getInOut().getOutMessages()) {
+                if (outMsg.getInfo() instanceof InternalMessageInfo) {
+                    InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo();
+                    valueOut = valueOut.add(intMsgInfo.getValue().getCoins());
+                }
+            }
         }
-      case 1:
-        {
-          return AccountStates.FROZEN;
+
+        return TransactionPrintInfo.builder()
+                .hash(hash)
+                .now(now)
+                .op(
+                        (isNull(op))
+                                ? "N/A"
+                                : (op.compareTo(BigInteger.ONE.negate()) != 0) ? op.toString(16) : "no body")
+                .type(
+                        nonNull(tx.getDescription())
+                                ? tx.getDescription().getClass().getSimpleName().substring(22)
+                                : "")
+                .valueIn(valueIn)
+                .valueOut(valueOut)
+                .totalFees(totalFees)
+                .storageFeesCollected(storageFeesCollected)
+                .storageDueFees(storageDueFees)
+                .storageStatus(storageStatus)
+                .computeSuccess(computeSuccess)
+                .computeGasFees(computeGasFees)
+                .computeVmSteps(computeVmSteps)
+                .actionSuccess(actionSuccess)
+                .actionTotalFwdFees(actionTotalFwdFees)
+                .actionTotalActionFees(actionTotalActionFees)
+                .actionTotalActions(totalActions)
+                .actionResultCode(actionResultCode)
+                .inForwardFee(inForwardFees)
+                .exitCode(exitCode)
+                .actionCode(actionCode)
+                .lt(lt)
+                .account(nonNull(tx.getAccountAddr()) ? tx.getAccountAddrShort() : "")
+                .build();
+    }
+
+    private StoragePhase getStoragePhase(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            return ((TransactionDescriptionOrdinary) txDesc).getStoragePhase();
+        } else if (txDesc instanceof TransactionDescriptionStorage) {
+            return ((TransactionDescriptionStorage) txDesc).getStoragePhase();
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            return ((TransactionDescriptionSplitPrepare) txDesc).getStoragePhase();
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            return ((TransactionDescriptionTickTock) txDesc).getStoragePhase();
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            return ((TransactionDescriptionMergeInstall) txDesc).getStoragePhase();
+        } else if (txDesc instanceof TransactionDescriptionMergePrepare) {
+            return ((TransactionDescriptionMergePrepare) txDesc).getStoragePhase();
         }
-      case 2:
-        {
-          return AccountStates.ACTIVE;
+        return null;
+    }
+
+
+    private ComputePhaseVM getComputePhaseVm(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            ComputePhase computePhase = ((TransactionDescriptionOrdinary) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase);
+            }
+
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            ComputePhase computePhase = ((TransactionDescriptionSplitPrepare) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase);
+            }
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            ComputePhase computePhase = ((TransactionDescriptionTickTock) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase);
+            }
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            ComputePhase computePhase = ((TransactionDescriptionMergeInstall) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase);
+            }
         }
-      case 3:
-        {
-          return AccountStates.NON_EXIST;
+        return null;
+    }
+
+    private ComputeSkipReason getComputePhaseSkipReason(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            ComputePhase computePhase = ((TransactionDescriptionOrdinary) txDesc).getComputePhase();
+            if (computePhase instanceof ComputeSkipReason) {
+                return ((ComputeSkipReason) computePhase);
+            }
+
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            ComputePhase computePhase = ((TransactionDescriptionSplitPrepare) txDesc).getComputePhase();
+            if (computePhase instanceof ComputeSkipReason) {
+                return ((ComputeSkipReason) computePhase);
+            }
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            ComputePhase computePhase = ((TransactionDescriptionTickTock) txDesc).getComputePhase();
+            if (computePhase instanceof ComputeSkipReason) {
+                return ((ComputeSkipReason) computePhase);
+            }
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            ComputePhase computePhase = ((TransactionDescriptionMergeInstall) txDesc).getComputePhase();
+            if (computePhase instanceof ComputeSkipReason) {
+                return ((ComputeSkipReason) computePhase);
+            }
         }
+        return null;
     }
-    return null;
-  }
-
-  public TransactionFees getTransactionFees() {
-    Transaction tx = this;
-
-    BigInteger totalFees =
-        nonNull(tx.getTotalFees()) ? tx.getTotalFees().getCoins() : BigInteger.ZERO;
-    BigInteger totalForwardFees = getForwardFees(tx.getDescription());
-    BigInteger computeFees = getComputeFees(tx.getDescription());
-
-    BigInteger inForwardFees = BigInteger.ZERO;
-    BigInteger valueIn = BigInteger.ZERO;
-    BigInteger valueOut = BigInteger.ZERO;
-    BigInteger op = null;
-    long exitCode = getExitCode(tx.getDescription());
-    long actionCode = getActionCode(tx.getDescription());
-    long totalActions = getTotalActions(tx.getDescription());
-    long now = tx.getNow();
-    BigInteger lt = tx.getLt();
-    long outMsgs = tx.getOutMsgCount();
-
-    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();
+    private ActionPhase getActionPhase(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            return ((TransactionDescriptionOrdinary) txDesc).getActionPhase();
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            return ((TransactionDescriptionSplitPrepare) txDesc).getActionPhase();
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            return ((TransactionDescriptionTickTock) txDesc).getActionPhase();
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            return ((TransactionDescriptionMergeInstall) txDesc).getActionPhase();
         }
+        return null;
+    }
 
-        if (inMsg.getInfo() instanceof InternalMessageInfo) {
-          valueIn = ((InternalMessageInfo) inMsg.getInfo()).getValue().getCoins();
-          inForwardFees = ((InternalMessageInfo) inMsg.getInfo()).getFwdFee();
+    private long getTotalActions(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            ActionPhase actionPhase = ((TransactionDescriptionOrdinary) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            ActionPhase actionPhase = ((TransactionDescriptionSplitPrepare) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            ActionPhase actionPhase = ((TransactionDescriptionTickTock) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            ActionPhase actionPhase = ((TransactionDescriptionMergeInstall) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
+        } else {
+            return -1;
         }
-      }
+    }
 
-      for (Message outMsg : tx.getInOut().getOutMessages()) {
-        if (outMsg.getInfo() instanceof InternalMessageInfo) {
-          InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo();
-          valueOut = valueOut.add(intMsgInfo.getValue().getCoins());
+    private long getActionCode(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            ActionPhase actionPhase = ((TransactionDescriptionOrdinary) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            ActionPhase actionPhase = ((TransactionDescriptionSplitPrepare) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            ActionPhase actionPhase = ((TransactionDescriptionTickTock) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            ActionPhase actionPhase = ((TransactionDescriptionMergeInstall) txDesc).getActionPhase();
+            return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
+        } else {
+            return -1;
         }
-      }
-    }
-
-    return TransactionFees.builder()
-        .op(
-            (isNull(op))
-                ? "N/A"
-                : (op.compareTo(BigInteger.ONE.negate()) != 0) ? op.toString(16) : "no body")
-        .type(
-            nonNull(tx.getDescription())
-                ? tx.getDescription().getClass().getSimpleName().substring(22)
-                : "")
-        .valueIn(valueIn)
-        .valueOut(valueOut)
-        .totalFees(totalFees)
-        .outForwardFee(totalForwardFees)
-        .computeFee(computeFees)
-        .inForwardFee(inForwardFees)
-        .exitCode(exitCode)
-        .actionCode(actionCode)
-        .totalActions(totalActions)
-        .now(now)
-        .lt(lt)
-        .account(nonNull(tx.getAccountAddr()) ? tx.getAccountAddrShort() : "")
-        .build();
-  }
-
-  private BigInteger getComputeFees(TransactionDescription txDesc) {
-    if (txDesc instanceof TransactionDescriptionOrdinary) {
-      ComputePhase computePhase = ((TransactionDescriptionOrdinary) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getGasFees();
-      }
-    } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
-      ComputePhase computePhase = ((TransactionDescriptionSplitPrepare) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getGasFees();
-      }
-    } else if (txDesc instanceof TransactionDescriptionTickTock) {
-      ComputePhase computePhase = ((TransactionDescriptionTickTock) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getGasFees();
-      }
-    } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
-      ComputePhase computePhase = ((TransactionDescriptionMergeInstall) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getGasFees();
-      }
-    } else {
-      return BigInteger.ZERO;
-    }
-    return BigInteger.ZERO;
-  }
-
-  private BigInteger getForwardFees(TransactionDescription txDesc) {
-    if (txDesc instanceof TransactionDescriptionOrdinary) {
-      ActionPhase actionPhase = ((TransactionDescriptionOrdinary) txDesc).getActionPhase();
-      return isNull(actionPhase) ? BigInteger.ZERO : actionPhase.getTotalFwdFees();
-    } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
-      ActionPhase actionPhase = ((TransactionDescriptionSplitPrepare) txDesc).getActionPhase();
-      return isNull(actionPhase) ? BigInteger.ZERO : actionPhase.getTotalFwdFees();
-    } else if (txDesc instanceof TransactionDescriptionTickTock) {
-      ActionPhase actionPhase = ((TransactionDescriptionTickTock) txDesc).getActionPhase();
-      return isNull(actionPhase) ? BigInteger.ZERO : actionPhase.getTotalFwdFees();
-    } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
-      ActionPhase actionPhase = ((TransactionDescriptionMergeInstall) txDesc).getActionPhase();
-      return isNull(actionPhase) ? BigInteger.ZERO : actionPhase.getTotalFwdFees();
-    } else {
-      return BigInteger.ZERO;
-    }
-  }
-
-  private long getTotalActions(TransactionDescription txDesc) {
-    if (txDesc instanceof TransactionDescriptionOrdinary) {
-      ActionPhase actionPhase = ((TransactionDescriptionOrdinary) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
-    } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
-      ActionPhase actionPhase = ((TransactionDescriptionSplitPrepare) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
-    } else if (txDesc instanceof TransactionDescriptionTickTock) {
-      ActionPhase actionPhase = ((TransactionDescriptionTickTock) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
-    } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
-      ActionPhase actionPhase = ((TransactionDescriptionMergeInstall) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getTotalActions();
-    } else {
-      return -1;
-    }
-  }
-
-  private long getActionCode(TransactionDescription txDesc) {
-    if (txDesc instanceof TransactionDescriptionOrdinary) {
-      ActionPhase actionPhase = ((TransactionDescriptionOrdinary) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
-    } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
-      ActionPhase actionPhase = ((TransactionDescriptionSplitPrepare) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
-    } else if (txDesc instanceof TransactionDescriptionTickTock) {
-      ActionPhase actionPhase = ((TransactionDescriptionTickTock) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
-    } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
-      ActionPhase actionPhase = ((TransactionDescriptionMergeInstall) txDesc).getActionPhase();
-      return isNull(actionPhase) ? 0 : actionPhase.getResultCode();
-    } else {
-      return -1;
-    }
-  }
-
-  private long getExitCode(TransactionDescription txDesc) {
-    if (txDesc instanceof TransactionDescriptionOrdinary) {
-      ComputePhase computePhase = ((TransactionDescriptionOrdinary) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
-      }
-    } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
-      ComputePhase computePhase = ((TransactionDescriptionSplitPrepare) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
-      }
-    } else if (txDesc instanceof TransactionDescriptionTickTock) {
-      ComputePhase computePhase = ((TransactionDescriptionTickTock) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
-      }
-    } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
-      ComputePhase computePhase = ((TransactionDescriptionMergeInstall) txDesc).getComputePhase();
-      if (computePhase instanceof ComputePhaseVM) {
-        return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
-      }
-    } else {
-      return -1;
-    }
-    return -1;
-  }
-
-  public void printTransactionFees() {
-    printTransactionFees(false, false);
-  }
-
-  public void printTransactionFees(boolean withHeader, boolean withFooter, String balance) {
-    TransactionFees txFees = getTransactionFees();
-
-    if (withHeader) {
-      printTxHeader(" (initial balance " + balance + ")");
-    }
-    printTxData(txFees);
-
-    if (withFooter) {
-      printTxFooter();
-    }
-  }
-
-  public void printTransactionFees(boolean withHeader, boolean withFooter) {
-    TransactionFees txFees = getTransactionFees();
-
-    if (withHeader) {
-      printTxHeader("");
-    }
-    printTxData(txFees);
-
-    if (withFooter) {
-      printTxFooter();
-    }
-  }
-
-  public List getAllMessageFees() {
-    List messageFees = new ArrayList<>();
-    Transaction tx = this;
-
-    BigInteger op;
-
-    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);
+    private long getExitCode(TransactionDescription txDesc) {
+        if (txDesc instanceof TransactionDescriptionOrdinary) {
+            ComputePhase computePhase = ((TransactionDescriptionOrdinary) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
+            }
+        } else if (txDesc instanceof TransactionDescriptionSplitPrepare) {
+            ComputePhase computePhase = ((TransactionDescriptionSplitPrepare) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
+            }
+        } else if (txDesc instanceof TransactionDescriptionTickTock) {
+            ComputePhase computePhase = ((TransactionDescriptionTickTock) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
+            }
+        } else if (txDesc instanceof TransactionDescriptionMergeInstall) {
+            ComputePhase computePhase = ((TransactionDescriptionMergeInstall) txDesc).getComputePhase();
+            if (computePhase instanceof ComputePhaseVM) {
+                return ((ComputePhaseVM) computePhase).getDetails().getExitCode();
+            }
         } else {
-          op = BigInteger.ONE.negate();
+            return -1;
+        }
+        return -1;
+    }
+
+
+    /**
+     * Print txs data without header, footer, balance and block.
+     */
+
+    public void printTransactionInfo() {
+        printTransactionInfo(false, false);
+    }
+
+    /**
+     * Print txs data without header, footer and balance, with block only.
+     *
+     * @param block String
+     */
+    public void printTransactionInfo(String block) {
+        printTransactionInfo(false, false, "", block);
+    }
+
+    /**
+     * Print txs data.
+     *
+     * @param withHeader boolean
+     * @param withFooter boolean
+     * @param balance    String
+     * @param block      String
+     */
+    public void printTransactionInfo(boolean withHeader, boolean withFooter, String balance, String block) {
+        TransactionPrintInfo txFees = getTransactionPrintInfo();
+
+        if (withHeader) {
+            if (StringUtils.isNotEmpty(balance)) {
+                printTxHeader(" (initial balance " + balance + ")");
+            } else {
+                printTxHeader("");
+            }
         }
+        printTxData(txFees, block);
 
-        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 (withFooter) {
+            printTxFooter();
         }
-        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);
+    }
+
+    public void printTransactionInfo(boolean withHeader, boolean withFooter, String balance) {
+        TransactionPrintInfo txFees = getTransactionPrintInfo();
+
+        if (withHeader) {
+            printTxHeaderWithoutBlock(" (initial balance " + balance + ")");
         }
-        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);
+        printTxData(txFees);
+
+        if (withFooter) {
+            printTxFooterWithoutBlock();
         }
-      }
+    }
 
-      for (Message outMsg : tx.getInOut().getOutMessages()) {
+    public void printTransactionInfo(boolean withHeader, boolean withFooter) {
+        TransactionPrintInfo txFees = getTransactionPrintInfo();
 
-        Cell body = outMsg.getBody();
+        if (withHeader) {
+            printTxHeaderWithoutBlock("");
+        }
+        printTxData(txFees);
+
+        if (withFooter) {
+            printTxFooterWithoutBlock();
+        }
+    }
 
+    private BigInteger getOperationFromBody(Cell body) {
         if (nonNull(body) && body.getBits().getUsedBits() >= 32) {
-          op = CellSlice.beginParse(body).preloadInt(32);
+            return CellSlice.beginParse(body).preloadInt(32);
         } else {
-          op = BigInteger.ONE.negate();
+            return 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);
+    private String getCommentFromBody(Cell body) {
+        try {
+            if (nonNull(body) && body.getBits().getUsedBits() >= 32) {
+                String comment = CellSlice.beginParse(body).skipBits(32).loadSnakeString();
+                if ((StringUtils.contains(comment, '\0')) || (StringUtils.contains(comment, '\1'))
+                        || (StringUtils.contains(comment, '\2')) || (StringUtils.contains(comment, '\3'))
+                        || (StringUtils.contains(comment, '\uFFFD'))) {
+                    return "";
+                } else {
+                    if (comment.length() > 26) {
+                        return comment.substring(0, 26);
+                    } else {
+                        return comment;
+                    }
+                }
+            } else {
+                return "";
+            }
+        } catch (Exception e) {
+            return "error";
         }
-        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);
+    }
+
+    public List getAllMessagePrintInfo() {
+        List messagePrintInfo = new ArrayList<>();
+        Transaction tx = this;
+
+        BigInteger op;
+        String comment;
+
+        if (nonNull(tx.getInOut())) {
+
+            Message inMsg = tx.getInOut().getIn();
+            if (nonNull(inMsg)) {
+                Cell body = inMsg.getBody();
+
+                op = getOperationFromBody(body);
+                comment = getCommentFromBody(body);
+
+                if (inMsg.getInfo() instanceof InternalMessageInfo) {
+                    InternalMessageInfo msgInfo = ((InternalMessageInfo) inMsg.getInfo());
+                    MessagePrintInfo msgPrintInfo =
+                            MessagePrintInfo.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())
+                                    .comment(comment)
+                                    .build();
+                    messagePrintInfo.add(msgPrintInfo);
+                }
+                if (inMsg.getInfo() instanceof ExternalMessageOutInfo) {
+                    ExternalMessageOutInfo msgInfo = ((ExternalMessageOutInfo) inMsg.getInfo());
+                    MessagePrintInfo msgPrintInfo =
+                            MessagePrintInfo.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())
+                                    .comment(comment)
+                                    .build();
+                    messagePrintInfo.add(msgPrintInfo);
+                }
+                if (inMsg.getInfo() instanceof ExternalMessageInInfo) {
+                    ExternalMessageInInfo msgInfo = ((ExternalMessageInInfo) inMsg.getInfo());
+                    MessagePrintInfo msgPrintInfo =
+                            MessagePrintInfo.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())
+                                    .comment(comment)
+                                    .build();
+                    messagePrintInfo.add(msgPrintInfo);
+                }
+            }
+
+            for (Message outMsg : tx.getInOut().getOutMessages()) {
+
+                Cell body = outMsg.getBody();
+
+                op = getOperationFromBody(body);
+                comment = getCommentFromBody(body);
+
+                if (outMsg.getInfo() instanceof InternalMessageInfo) {
+                    InternalMessageInfo intMsgInfo = (InternalMessageInfo) outMsg.getInfo();
+
+                    MessagePrintInfo msgPrintInfo =
+                            MessagePrintInfo.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())
+                                    .comment(comment)
+                                    .build();
+                    messagePrintInfo.add(msgPrintInfo);
+                }
+                if (outMsg.getInfo() instanceof ExternalMessageOutInfo) {
+                    ExternalMessageOutInfo outMsgInfo = (ExternalMessageOutInfo) outMsg.getInfo();
+
+                    MessagePrintInfo msgPrintInfo =
+                            MessagePrintInfo.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())
+                                    .comment(comment)
+                                    .build();
+                    messagePrintInfo.add(msgPrintInfo);
+                }
+                if (outMsg.getInfo() instanceof ExternalMessageInInfo) {
+                    ExternalMessageInInfo outMsgInfo = (ExternalMessageInInfo) outMsg.getInfo();
+
+                    MessagePrintInfo msgPrintInfo =
+                            MessagePrintInfo.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())
+                                    .comment(comment)
+                                    .build();
+                    messagePrintInfo.add(msgPrintInfo);
+                }
+            }
         }
-        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 messagePrintInfo;
+    }
+
+    public void printAllMessages(boolean withHeader) {
+        printAllMessages(withHeader, false);
+    }
+
+    public void printAllMessages(boolean withHeader, boolean withFooter) {
+        List msgsPrintInfo = getAllMessagePrintInfo();
+        if (msgsPrintInfo.isEmpty()) {
+            log.info("No messages");
+            return;
         }
-      }
-    }
-    return messageFees;
-  }
-
-  public void printAllMessages(boolean withHeader) {
-    List msgFees = getAllMessageFees();
-    if (msgFees.isEmpty()) {
-      log.info("No messages");
-      return;
-    }
-
-    if (withHeader) {
-      MessageFees.printMessageFeesHeader();
-    }
-
-    for (MessageFees msgFee : msgFees) {
-      msgFee.printMessageFees();
-    }
-    MessageFees.printMessageFeesFooter();
-  }
-
-  public static void printTxHeader(String balance) {
-    log.info("");
-    log.info("Transactions" + balance);
-    log.info(
-        "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
-    log.info(
-        "| op       | type         | valueIn        | valueOut       | totalFees    | inForwardFee | outForwardFee | outActions | outMsgs | computeFee    | exitCode | actionCode | account       |");
-    log.info(
-        "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
-  }
-
-  public static void printTxFooter() {
-    log.info(
-        "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
-  }
-
-  public static void printTxData(TransactionFees txFees) {
-    String str =
-        String.format(
-            "| %-9s| %-13s| %-15s| %-15s| %-13s| %-13s| %-14s| %-11s| %-8s| %-14s| %-9s| %-11s| %-13s |",
-            txFees.getOp(),
-            txFees.getType(),
-            Utils.formatNanoValueZero(txFees.getValueIn()),
-            Utils.formatNanoValueZero(txFees.getValueOut()),
-            Utils.formatNanoValueZero(txFees.getTotalFees()),
-            Utils.formatNanoValueZero(txFees.getInForwardFee()),
-            Utils.formatNanoValueZero(txFees.getOutForwardFee()),
-            txFees.getTotalActions(),
-            txFees.getOutMsgs(),
-            Utils.formatNanoValueZero(txFees.getComputeFee()),
-            txFees.getExitCode(),
-            txFees.getActionCode(),
-            txFees.getAccount());
-    log.info(str);
-  }
-
-  private String formatMsgType(String fullMsgType) {
-    if (fullMsgType.equals("InternalMessageInfo")) {
-      return "internal-in";
-    } else if (fullMsgType.equals("ExternalMessageOutInfo")) {
-      return "external-out";
-    } else {
-      return "external-in";
-    }
-  }
+
+        if (withHeader) {
+            MessagePrintInfo.printMessageInfoHeader();
+        }
+
+        for (MessagePrintInfo msgPrintInfo : msgsPrintInfo) {
+            msgPrintInfo.printMessageInfo();
+        }
+        if (withFooter) {
+            MessagePrintInfo.printMessageInfoFooter();
+        }
+    }
+
+    public static void printTxHeader(String balance) {
+        log.info("");
+        log.info("Transactions" + balance);
+
+        log.info(StringUtils.repeat("-", txHeaderInfoWithBlock.length()));
+        log.info(txHeaderInfoWithBlockTop);
+        log.info(txHeaderInfoWithBlock);
+        log.info(StringUtils.repeat("-", txHeaderInfoWithBlock.length()));
+    }
+
+    public static void printTxHeaderWithoutBlock(String balance) {
+        log.info("");
+        log.info("Transactions" + balance);
+
+        log.info(StringUtils.repeat("-", txHeaderInfoWithoutBlock.length()));
+        log.info(txHeaderInfoWithoutBlockTop);
+        log.info(txHeaderInfoWithoutBlock);
+        log.info(StringUtils.repeat("-", txHeaderInfoWithoutBlock.length()));
+    }
+
+    public static void printTxFooter() {
+        log.info(StringUtils.repeat("-", txHeaderInfoWithBlock.length()));
+    }
+
+    public static void printTxFooterWithoutBlock() {
+        log.info(StringUtils.repeat("-", txHeaderInfoWithoutBlock.length()));
+    }
+
+    public static void printTxData(TransactionPrintInfo txPrintInfo) {
+        String str =
+                String.format(
+                        "| %-8s| %-10s| %-15s| %-10s| %-13s| %-15s| %-15s| %-13s| %-13s| %-14s| %-8s| %-14s| %-8s| %-9s| %-8s| %-13s| %-13s| %-8s| %-9s| %-14s|",
+                        txPrintInfo.getHash().substring(0, 6),
+                        Utils.toUTCTimeOnly(txPrintInfo.getNow()),
+                        txPrintInfo.getLt(),
+                        txPrintInfo.getOp(),
+                        txPrintInfo.getType(),
+                        Utils.formatNanoValueZero(txPrintInfo.getValueIn()),
+                        Utils.formatNanoValueZero(txPrintInfo.getValueOut()),
+                        Utils.formatNanoValueZero(txPrintInfo.getTotalFees()),
+                        Utils.formatNanoValueZero(txPrintInfo.getStorageFeesCollected()),
+                        Utils.formatNanoValueZero(txPrintInfo.getStorageDueFees()),
+//                        txPrintInfo.getStorageStatus(),
+                        txPrintInfo.getComputeSuccess(),
+                        Utils.formatNanoValueZero(txPrintInfo.getComputeGasFees()),
+                        txPrintInfo.getComputeVmSteps(),
+                        Utils.formatNanoValueZero(txPrintInfo.getComputeExitCode()),
+                        txPrintInfo.getActionSuccess(),
+                        Utils.formatNanoValueZero(txPrintInfo.getActionTotalFwdFees()),
+                        Utils.formatNanoValueZero(txPrintInfo.getActionTotalActionFees()),
+                        txPrintInfo.getActionTotalActions(),
+                        txPrintInfo.getActionResultCode(),
+                        txPrintInfo.getAccount());
+        log.info(str);
+    }
+
+    public static void printTxData(TransactionPrintInfo txPrintInfo, String block) {
+        String str =
+                String.format(
+                        "| %-8s| %-10s| %-15s| %-10s| %-13s| %-15s| %-15s| %-13s| %-13s| %-14s| %-8s| %-14s| %-8s| %-9s| %-8s| %-13s| %-13s| %-8s| %-9s| %-14s| %-32s|",
+                        txPrintInfo.getHash().substring(0, 5),
+                        Utils.toUTCTimeOnly(txPrintInfo.getNow()),
+                        txPrintInfo.getLt(),
+                        txPrintInfo.getOp(),
+                        txPrintInfo.getType(),
+                        Utils.formatNanoValueZero(txPrintInfo.getValueIn()),
+                        Utils.formatNanoValueZero(txPrintInfo.getValueOut()),
+                        Utils.formatNanoValueZero(txPrintInfo.getTotalFees()),
+                        Utils.formatNanoValueZero(txPrintInfo.getStorageFeesCollected()),
+                        Utils.formatNanoValueZero(txPrintInfo.getStorageDueFees()),
+//                        txPrintInfo.getStorageStatus(),
+                        txPrintInfo.getComputeSuccess(),
+                        Utils.formatNanoValueZero(txPrintInfo.getComputeGasFees()),
+                        txPrintInfo.getComputeVmSteps(),
+                        Utils.formatNanoValueZero(txPrintInfo.getComputeExitCode()),
+                        txPrintInfo.getActionSuccess(),
+                        Utils.formatNanoValueZero(txPrintInfo.getActionTotalFwdFees()),
+                        Utils.formatNanoValueZero(txPrintInfo.getActionTotalActionFees()),
+                        txPrintInfo.getActionTotalActions(),
+                        txPrintInfo.getActionResultCode(),
+                        txPrintInfo.getAccount(),
+                        StringUtils.isEmpty(block) ? "N/A" : block);
+        log.info(str);
+    }
+
+    private String formatMsgType(String fullMsgType) {
+        if (fullMsgType.equals("InternalMessageInfo")) {
+            return "internal-in";
+        } else if (fullMsgType.equals("ExternalMessageOutInfo")) {
+            return "external-out";
+        } else {
+            return "external-in";
+        }
+    }
 }
diff --git a/cell/src/main/java/org/ton/java/tlb/types/TransactionDescription.java b/cell/src/main/java/org/ton/java/tlb/types/TransactionDescription.java
index 01cd1c31..f035927f 100644
--- a/cell/src/main/java/org/ton/java/tlb/types/TransactionDescription.java
+++ b/cell/src/main/java/org/ton/java/tlb/types/TransactionDescription.java
@@ -4,102 +4,100 @@
 import org.ton.java.cell.CellSlice;
 
 /**
- *
- *
  * 
  * trans_ord$0000 credit_first:Bool
  *   storage_ph:(Maybe TrStoragePhase)
  *   credit_ph:(Maybe TrCreditPhase)
- *   compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
- *   aborted:Bool bounce:(Maybe TrBouncePhase)
+ *   compute_ph:TrComputePhase
+ *   action:(Maybe ^TrActionPhase)
+ *   aborted:Bool
+ *   bounce:(Maybe TrBouncePhase)
  *   destroyed:Bool
  *   = TransactionDescr;
  *
- * trans_storage$0001 storage_ph:TrStoragePhase
+ * trans_storage$0001
+ *   storage_ph:TrStoragePhase
  *   = TransactionDescr;
  *
- * trans_tick_tock$001 is_tock:Bool storage_ph:TrStoragePhase
- *   compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
- *   aborted:Bool destroyed:Bool = TransactionDescr;
- * //
- * split_merge_info$_ cur_shard_pfx_len:(## 6)
- *   acc_split_depth:(## 6) this_addr:bits256 sibling_addr:bits256
+ * trans_tick_tock$001 is_tock:Bool
+ *   storage_ph:TrStoragePhase
+ *   compute_ph:TrComputePhase
+ *   action:(Maybe ^TrActionPhase)
+ *   aborted:Bool
+ *   destroyed:Bool = TransactionDescr;
+ *
+ * split_merge_info$_
+ *   cur_shard_pfx_len:(## 6)
+ *   acc_split_depth:(## 6)
+ *   this_addr:bits256
+ *   sibling_addr:bits256
  *   = SplitMergeInfo;
- * trans_split_prepare$0100 split_info:SplitMergeInfo
+ *
+ * trans_split_prepare$0100
+ *   split_info:SplitMergeInfo
  *   storage_ph:(Maybe TrStoragePhase)
- *   compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
- *   aborted:Bool destroyed:Bool
+ *   compute_ph:TrComputePhase
+ *   action:(Maybe ^TrActionPhase)
+ *   aborted:Bool
+ *   destroyed:Bool
  *   = TransactionDescr;
- * trans_split_install$0101 split_info:SplitMergeInfo
+ *
+ * trans_split_install$0101
+ *   split_info:SplitMergeInfo
  *   prepare_transaction:^Transaction
  *   installed:Bool = TransactionDescr;
  *
- * trans_merge_prepare$0110 split_info:SplitMergeInfo
- *   storage_ph:TrStoragePhase aborted:Bool
+ * trans_merge_prepare$0110
+ * split_info:SplitMergeInfo
+ *   storage_ph:TrStoragePhase
+ *   aborted:Bool
  *   = TransactionDescr;
  *
  * trans_merge_install$0111 split_info:SplitMergeInfo
  *   prepare_transaction:^Transaction
  *   storage_ph:(Maybe TrStoragePhase)
  *   credit_ph:(Maybe TrCreditPhase)
- *   compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)
- *   aborted:Bool destroyed:Bool
+ *   compute_ph:TrComputePhase
+ *   action:(Maybe ^TrActionPhase)
+ *   aborted:Bool
+ *   destroyed:Bool
  *   = TransactionDescr;
  *   
*/ public interface TransactionDescription { - Cell toCell(); - - // { - // CellBuilder c = CellBuilder.beginCell(); - // - // if (description instanceof TransactionDescriptionStorage) { - // c.storeUint(0b0001, 3); - // c.storeSlice(CellSlice.beginParse(((TransactionDescriptionStorage) - // description).toCell())); - // } else if (description instanceof TransactionDescriptionOrdinary) { - // c.storeUint(0b000, 3); - // c.storeSlice(CellSlice.beginParse(((TransactionDescriptionOrdinary) - // description).toCell())); - // } - // return c.endCell(); - // } + Cell toCell(); - static TransactionDescription deserialize(CellSlice cs) { - int pfx = cs.preloadUint(3).intValue(); - switch (pfx) { - case 0b000: - { - boolean isStorage = cs.preloadBit(); - if (isStorage) { - return TransactionDescriptionStorage.deserialize(cs); - } - return TransactionDescriptionOrdinary.deserialize(cs); - } - case 0b001: - { - return TransactionDescriptionTickTock.deserialize(cs); - } - case 0b010: - { - boolean isInstall = cs.preloadBit(); - if (isInstall) { - return TransactionDescriptionSplitInstall.deserialize(cs); - } - return TransactionDescriptionSplitPrepare.deserialize(cs); - } - case 0b011: - { - boolean isInstall = cs.preloadBit(); - if (isInstall) { - return TransactionDescriptionMergeInstall.deserialize(cs); - } - return TransactionDescriptionMergePrepare.deserialize(cs); + static TransactionDescription deserialize(CellSlice cs) { + int pfx = cs.preloadUint(3).intValue(); + switch (pfx) { + case 0b000: { + boolean isStorage = cs.preloadBit(); + if (isStorage) { + return TransactionDescriptionStorage.deserialize(cs); + } + return TransactionDescriptionOrdinary.deserialize(cs); + } + case 0b001: { + return TransactionDescriptionTickTock.deserialize(cs); + } + case 0b010: { + boolean isInstall = cs.preloadBit(); + if (isInstall) { + return TransactionDescriptionSplitInstall.deserialize(cs); + } + return TransactionDescriptionSplitPrepare.deserialize(cs); + } + case 0b011: { + boolean isInstall = cs.preloadBit(); + if (isInstall) { + return TransactionDescriptionMergeInstall.deserialize(cs); + } + return TransactionDescriptionMergePrepare.deserialize(cs); + } } + throw new Error( + "unknown transaction description type (must be in range [0..3], found 0x" + + Integer.toBinaryString(pfx)); } - throw new Error( - "unknown transaction description type (must be in range [0..3], found 0x" - + Integer.toBinaryString(pfx)); - } } diff --git a/cell/src/main/java/org/ton/java/tlb/types/TransactionFees.java b/cell/src/main/java/org/ton/java/tlb/types/TransactionFees.java deleted file mode 100644 index 5d05ea9e..00000000 --- a/cell/src/main/java/org/ton/java/tlb/types/TransactionFees.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.ton.java.tlb.types; - -import java.math.BigInteger; -import lombok.Builder; -import lombok.Data; - -@Builder -@Data -public class TransactionFees { - String op; - String type; - BigInteger totalFees; - BigInteger computeFee; - BigInteger inForwardFee; - BigInteger outForwardFee; - BigInteger valueIn; - BigInteger valueOut; - long exitCode; - long actionCode; - long totalActions; - long outMsgs; - long now; - BigInteger lt; - String account; - BigInteger balance; -} diff --git a/cell/src/main/java/org/ton/java/tlb/types/TransactionPrintInfo.java b/cell/src/main/java/org/ton/java/tlb/types/TransactionPrintInfo.java new file mode 100644 index 00000000..df6ac934 --- /dev/null +++ b/cell/src/main/java/org/ton/java/tlb/types/TransactionPrintInfo.java @@ -0,0 +1,41 @@ +package org.ton.java.tlb.types; + +import lombok.Builder; +import lombok.Data; + +import java.math.BigInteger; + +@Builder +@Data +public class TransactionPrintInfo { + String hash; // in_msg.hash + long now; + String op; + String type; + BigInteger valueIn; + BigInteger valueOut; + + BigInteger totalFees; + BigInteger storageFeesCollected; + BigInteger storageDueFees; + String storageStatus; + String computeSuccess; + BigInteger computeGasFees; + BigInteger computeGasUsed; + long computeVmSteps; + BigInteger computeExitCode; + String actionSuccess; + BigInteger actionTotalFwdFees; + BigInteger actionTotalActionFees; + long actionTotalActions; + long actionResultCode; + BigInteger inForwardFee; + BigInteger outForwardFee; + long exitCode; + long actionCode; + long outMsgs; + BigInteger lt; + String account; + BigInteger balance; + Transaction tx; +} diff --git a/cell/src/test/java/org/ton/java/tlb/TestTlbTransactionReader.java b/cell/src/test/java/org/ton/java/tlb/TestTlbTransactionReader.java index 384f7a1e..35b1b582 100644 --- a/cell/src/test/java/org/ton/java/tlb/TestTlbTransactionReader.java +++ b/cell/src/test/java/org/ton/java/tlb/TestTlbTransactionReader.java @@ -29,4 +29,24 @@ public void testLoadTransaction2() { Transaction transaction = Transaction.deserialize(cs); log.info("transaction {}", transaction); } + + @Test + public void testLoadTransactionPrintWithBlock() { + Cell c = CellBuilder.beginCell().fromBoc("b5ee9c72010226010006990003b570c6e8053cae2db8db1f757877a20451406d17f8ab7e42b88aa3bf6022dd2666200002018ba3f1404177290fd7520f4c9a9cdea0d5c1d972e0f63b75e4114ca8ec24c20211342379800002018ba208f8163eb5649000347372d2680102030201e0040500827292c274ccb4edfb07eeffce3721febf61bb2666d7ee4234f9e01a59b9e8a2a97129422e88bc846f3e65e2c7a05f4ac0954cf243cb7dff41b59bd42138c835a95b02170c40491f4add40186e668611242503b148001b5ba243fca4eba58d090c2fdbcfd5468567018240568edc715af856360479fb00031ba014f2b8b6e36c7dd5e1de88114501b45fe2adf90ae22a8efd808b74999891f4add40006ff7ec000004031747e2806c7d6ac931b0607080101df150114ff00f4a413f4bcf2c80b090059000000000000000000000000bb870617fcc0c46817b359c9399b9bb71b944947102674e4b46a8a9312191735400199285e6041bb8cfb5d60ea1bd3956f9b77a026cfbe07217d221a024b8a12e7fca30bc9c605d27755caba9ae0a66f3494952fdb788f65ba15e99ea1c4148727ec020000000063eb56833a288aabc0130201200a0b0201480c0d0006f2f0010202cf0e0f020120111200231b0c4835d26040982e64cc3e0024bc0078a001e920c235c60834c7f4cffe08ea87d4c82e7c98fb513434c7f4cff4fffd013454d820103d039be84c7c98145ceebca881fe40550421fe443ca8c0bd01347e001fe3858860043d1e1be9482600b4c1f50c007ec0244cb8806cf996e0c96872100d20103d10e2b98c407232c7c4f2cff2fffd00327b5520100034208040f4966fa56c122094305303b9de2093333601926c21e2b30017bd9ce76a26869af98eb85ffc0041be5f976a268698f98e99fe9ff98fa0268a91040207a0737d098c92dbfc95dd1f140104d08014026162007bb97b0fd056eabbb2d09d36ae533b16f545d0fbfbf187685c7c6a115d6d303d000000000000000000000000000232161702b1680018dd00a795c5b71b63eeaf0ef4408a280da2ff156fc857115477ec045ba4ccc5003ddcbd87e82b755dd9684e9b57299d8b7aa2e87dfdf8c3b42e3e3508aeb6981e91f0fc64bc06a18a7c00004031747e280ac7d6ac931916170114ff00f4a413f4bcf2c80b1801d931f5ab23c00585d8b57d25ff490c78aef4d63589f930b510d6e0009ccecfc503eb3c723c362801ca8151271aafc451be2c28cdc132ddc423328db0830c9afb19e99a6d6b62d19500036b74487f949d74b1a12185fb79faa8d0ace030480ad1db8e2b5f0ac6c08f3f50ee6b280223020120191a0201481b1c0004f2300202cd1d1e0051a03859da89a1a601a63ff481f481f481f401a861a1f481f401f481f4006104208c92b0a0158002ab0102f7d00e8698180b8d8492f82707d201876a2686980698ffd207d207d207d006a18136000f968ca116ba4e10159c720191c1c29a0e382c92f847028a26382f970fa02698fc1080289c6c8895d7970fae99f98fd2018202b036465800ae58fa801e78b00e78b00e78b00fd016664f6aa701b13e380718103e98fe99f9810c1f2001f7660840ee6b280149828148c2fbcb87089343e903e803e903e800c14e4a848685421e845a814a41c20043232c15400f3c5807e80b2dab25c7ec00970800975d27080ac2385d4115c20043232c15400f3c5807e80b2dab25c7ec00408e48d0d38969c20043232c15400f3c5807e80b2dab25c7ec01c08208417f30f452220016371038476514433070f005014ac001925f0be021c0029f31104910384760102510241023f005e03ac003e3025f09840ff2f02100ca82103b9aca0018bef2e1c95346c7055152c70515b1f2e1ca702082105fcc3d14218010c8cb0528cf1621fa02cb6acb1f19cb3f27cf1627cf1618ca0027fa0217ca00c98040fb0071065044451506c8cb0015cb1f5003cf1601cf1601cf1601fa02ccc9ed540082218018c8cb052acf1621fa02cb6acb1f13cb3f23cf165003cf16ca0021fa02ca00c98306fb0071555006c8cb0015cb1f5003cf1601cf1601cf1601fa02ccc9ed5400878001b5ba243fca4eba58d090c2fdbcfd5468567018240568edc715af856360479fa100036b74487f949d74b1a12185fb79faa8d0ace030480ad1db8e2b5f0ac6c08f3f42009e43afcc3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc9bc93d04ca1898800000000000200000000000362a1ec2a403ce96f3234341d66f0c8f2245dfda3293444eca58168c5d17c911643d0c35c").endCell(); + + CellSlice cs = CellSlice.beginParse(c); + Transaction transaction = Transaction.deserialize(cs); + log.info("transaction {}", transaction); + transaction.printTransactionInfo(true, true, "", "(0,600000000000000,233232)"); + } + + @Test + public void testLoadTransactionPrintNoBlock() { + Cell c = CellBuilder.beginCell().fromBoc("b5ee9c72010226010006990003b570c6e8053cae2db8db1f757877a20451406d17f8ab7e42b88aa3bf6022dd2666200002018ba3f1404177290fd7520f4c9a9cdea0d5c1d972e0f63b75e4114ca8ec24c20211342379800002018ba208f8163eb5649000347372d2680102030201e0040500827292c274ccb4edfb07eeffce3721febf61bb2666d7ee4234f9e01a59b9e8a2a97129422e88bc846f3e65e2c7a05f4ac0954cf243cb7dff41b59bd42138c835a95b02170c40491f4add40186e668611242503b148001b5ba243fca4eba58d090c2fdbcfd5468567018240568edc715af856360479fb00031ba014f2b8b6e36c7dd5e1de88114501b45fe2adf90ae22a8efd808b74999891f4add40006ff7ec000004031747e2806c7d6ac931b0607080101df150114ff00f4a413f4bcf2c80b090059000000000000000000000000bb870617fcc0c46817b359c9399b9bb71b944947102674e4b46a8a9312191735400199285e6041bb8cfb5d60ea1bd3956f9b77a026cfbe07217d221a024b8a12e7fca30bc9c605d27755caba9ae0a66f3494952fdb788f65ba15e99ea1c4148727ec020000000063eb56833a288aabc0130201200a0b0201480c0d0006f2f0010202cf0e0f020120111200231b0c4835d26040982e64cc3e0024bc0078a001e920c235c60834c7f4cffe08ea87d4c82e7c98fb513434c7f4cff4fffd013454d820103d039be84c7c98145ceebca881fe40550421fe443ca8c0bd01347e001fe3858860043d1e1be9482600b4c1f50c007ec0244cb8806cf996e0c96872100d20103d10e2b98c407232c7c4f2cff2fffd00327b5520100034208040f4966fa56c122094305303b9de2093333601926c21e2b30017bd9ce76a26869af98eb85ffc0041be5f976a268698f98e99fe9ff98fa0268a91040207a0737d098c92dbfc95dd1f140104d08014026162007bb97b0fd056eabbb2d09d36ae533b16f545d0fbfbf187685c7c6a115d6d303d000000000000000000000000000232161702b1680018dd00a795c5b71b63eeaf0ef4408a280da2ff156fc857115477ec045ba4ccc5003ddcbd87e82b755dd9684e9b57299d8b7aa2e87dfdf8c3b42e3e3508aeb6981e91f0fc64bc06a18a7c00004031747e280ac7d6ac931916170114ff00f4a413f4bcf2c80b1801d931f5ab23c00585d8b57d25ff490c78aef4d63589f930b510d6e0009ccecfc503eb3c723c362801ca8151271aafc451be2c28cdc132ddc423328db0830c9afb19e99a6d6b62d19500036b74487f949d74b1a12185fb79faa8d0ace030480ad1db8e2b5f0ac6c08f3f50ee6b280223020120191a0201481b1c0004f2300202cd1d1e0051a03859da89a1a601a63ff481f481f481f401a861a1f481f401f481f4006104208c92b0a0158002ab0102f7d00e8698180b8d8492f82707d201876a2686980698ffd207d207d207d006a18136000f968ca116ba4e10159c720191c1c29a0e382c92f847028a26382f970fa02698fc1080289c6c8895d7970fae99f98fd2018202b036465800ae58fa801e78b00e78b00e78b00fd016664f6aa701b13e380718103e98fe99f9810c1f2001f7660840ee6b280149828148c2fbcb87089343e903e803e903e800c14e4a848685421e845a814a41c20043232c15400f3c5807e80b2dab25c7ec00970800975d27080ac2385d4115c20043232c15400f3c5807e80b2dab25c7ec00408e48d0d38969c20043232c15400f3c5807e80b2dab25c7ec01c08208417f30f452220016371038476514433070f005014ac001925f0be021c0029f31104910384760102510241023f005e03ac003e3025f09840ff2f02100ca82103b9aca0018bef2e1c95346c7055152c70515b1f2e1ca702082105fcc3d14218010c8cb0528cf1621fa02cb6acb1f19cb3f27cf1627cf1618ca0027fa0217ca00c98040fb0071065044451506c8cb0015cb1f5003cf1601cf1601cf1601fa02ccc9ed540082218018c8cb052acf1621fa02cb6acb1f13cb3f23cf165003cf16ca0021fa02ca00c98306fb0071555006c8cb0015cb1f5003cf1601cf1601cf1601fa02ccc9ed5400878001b5ba243fca4eba58d090c2fdbcfd5468567018240568edc715af856360479fa100036b74487f949d74b1a12185fb79faa8d0ace030480ad1db8e2b5f0ac6c08f3f42009e43afcc3d090000000000000000007e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc9bc93d04ca1898800000000000200000000000362a1ec2a403ce96f3234341d66f0c8f2245dfda3293444eca58168c5d17c911643d0c35c").endCell(); + + CellSlice cs = CellSlice.beginParse(c); + Transaction transaction = Transaction.deserialize(cs); + log.info("transaction {}", transaction); + transaction.printTransactionInfo(true, true, ""); + } } \ No newline at end of file diff --git a/emulator/src/test/java/org/ton/java/emulator/TestTxEmulator.java b/emulator/src/test/java/org/ton/java/emulator/TestTxEmulator.java index a6fa6999..59a0f043 100644 --- a/emulator/src/test/java/org/ton/java/emulator/TestTxEmulator.java +++ b/emulator/src/test/java/org/ton/java/emulator/TestTxEmulator.java @@ -1,19 +1,7 @@ package org.ton.java.emulator; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; - import com.iwebpp.crypto.TweetNaclFast; import com.sun.jna.Native; -import java.io.IOException; -import java.math.BigInteger; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.junit.BeforeClass; @@ -39,707 +27,720 @@ import org.ton.java.tonlib.types.SmcLibraryResult; import org.ton.java.utils.Utils; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + @Slf4j @RunWith(JUnit4.class) public class TestTxEmulator { - static TxEmulator txEmulator; - static Tonlib tonlib; - static Cell config; - static LiteClient liteClient; - - static Account testAccount; - - @BeforeClass - public static void setUpBeforeClass() { - liteClient = LiteClient.builder().build(); - - tonlib = Tonlib.builder().testnet(true).ignoreCache(false).build(); - - // config = tonlib.getConfigAll(128); - - txEmulator = - TxEmulator.builder() - .configType(TxEmulatorConfig.TESTNET) - .verbosityLevel(TxVerbosityLevel.TRUNCATED) - .build(); - - testAccount = - Account.builder() - .isNone(false) - .address( - MsgAddressIntStd.of( - "-1:0000000000000000000000000000000000000000000000000000000000000000")) - .storageInfo( - StorageInfo.builder() - .storageUsed( - StorageUsed.builder() - .cellsUsed(BigInteger.ZERO) - .bitsUsed(BigInteger.ZERO) - .publicCellsUsed(BigInteger.ZERO) - .build()) - .lastPaid(System.currentTimeMillis() / 1000) - .duePayment(Utils.toNano(2)) - .build()) - .accountStorage( - AccountStorage.builder() - .balance( - CurrencyCollection.builder() - .coins(Utils.toNano(2)) // initial balance - .build()) - .accountState( - AccountStateActive.builder() - .stateInit( - StateInit.builder() - .code( - CellBuilder.beginCell() - .fromBoc( - "b5ee9c7241010101004e000098ff0020dd2082014c97ba9730ed44d0d70b1fe0a4f260810200d71820d70b1fed44d0d31fd3ffd15112baf2a122f901541044f910f2a2f80001d31f31d307d4d101fb00a4c8cb1fcbffc9ed5470102286") - .endCell()) - // .data(CellBuilder.beginCell().storeBit(true).endCell()) - .build()) - .build()) - .accountStatus("ACTIVE") - .build()) - .build(); - } - - @Test - public void testInitTxEmulator() throws IOException { - - String configAllTestnet = - IOUtils.toString( - Objects.requireNonNull( - TestTxEmulator.class.getResourceAsStream("/config-all-testnet.txt")), - StandardCharsets.UTF_8); - - TxEmulatorI txEmulatorI = - Native.load(Utils.detectAbsolutePath("emulator", true), TxEmulatorI.class); - long emulator = txEmulatorI.transaction_emulator_create(configAllTestnet, 2); - assertNotEquals(0, emulator); - } - - @Test - public void testSetVerbosityLevel() { - txEmulator.setVerbosityLevel(4); - } - - @Test - public void testSetDebugEnabled() { - assertTrue(txEmulator.setDebugEnabled(false)); - } - - @Test - public void testCustomConfig() throws IOException { - - String configAllTestnet = - IOUtils.toString( - Objects.requireNonNull( - TestTxEmulator.class.getResourceAsStream("/config-all-testnet.txt")), - StandardCharsets.UTF_8); - - txEmulator = - TxEmulator.builder() - .configType(TxEmulatorConfig.CUSTOM) - .customConfig(configAllTestnet) - .verbosityLevel(TxVerbosityLevel.UNLIMITED) - .build(); - txEmulator.setVerbosityLevel(4); - } - - @Test - public void testTxEmulatorIgnoreCheckSignature() { - assertTrue(txEmulator.setIgnoreCheckSignature(true)); - } - - @Test - public void testTxEmulatorSetLt() { - assertTrue(txEmulator.setEmulatorLt(200000)); - } - - @Test - public void testTxEmulatorSetRandSeed() { - assertTrue(txEmulator.setRandSeed(Utils.sha256("ABC"))); - } - - @Test - public void testTxEmulatorSetUnixTime() { - assertTrue(txEmulator.setUnixTime(System.currentTimeMillis() / 1000)); - } - - @Test - public void testTxEmulatorSetConfig() { - assertTrue(txEmulator.setConfig(tonlib.getConfigAll(128).toBase64())); - } - - @Test - public void testTxEmulatorCreateDestroyConfig() { - String configBase64 = tonlib.getConfigAll(128).toBase64(); - long config = txEmulator.createConfig(configBase64); - txEmulator.destroyConfig(config); - } - - @Test - public void testTxEmulatorSetLibs() { - Cell dictLibs = getLibs(); - - log.info("txEmulator.setLibs() result {}", txEmulator.setLibs(dictLibs.toBase64())); - } - - @Test - public void testTxEmulatorEmulateTickTx() { - ShardAccount shardAccount = - ShardAccount.builder() - .account(testAccount) - .lastTransHash(BigInteger.valueOf(2)) - .lastTransLt(BigInteger.ZERO) - .build(); - - log.info("shardAccount: {}", shardAccount); - String shardAccountBocBase64 = shardAccount.toCell().toBase64(); - log.info("shardAccountCellBocBase64: {}", shardAccountBocBase64); - EmulateTransactionResult result = - txEmulator.emulateTickTockTransaction(shardAccountBocBase64, false); - log.info("result {}", result); - assertThat(result.success).isTrue(); - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - log.info("vm log {}", result.getVm_log()); - } - - @Test - public void testTxEmulatorEmulateTockTx() { - ShardAccount shardAccount = - ShardAccount.builder() - .account(testAccount) - .lastTransHash(BigInteger.valueOf(2)) - .lastTransLt(BigInteger.ZERO) - .build(); - - log.info("shardAccount: {}", shardAccount); - String shardAccountBocBase64 = shardAccount.toCell().toBase64(); - log.info("shardAccountCellBocBase64: {}", shardAccountBocBase64); - EmulateTransactionResult result = - txEmulator.emulateTickTockTransaction(shardAccountBocBase64, true); - log.info("result {}", result); - assertThat(result.success).isTrue(); - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - } - - @Test - public void testTxEmulatorEmulateTxWithEmptyAccount() { - - ShardAccount shardAccount = - ShardAccount.builder() - .account(Account.builder().isNone(true).build()) - .lastTransHash(BigInteger.ZERO) - .lastTransLt(BigInteger.ZERO) - .build(); - String shardAccountBocBase64 = shardAccount.toCell().toBase64(); - - Message internalMsg = - Message.builder() - .info( - InternalMessageInfo.builder() - .srcAddr( - MsgAddressIntStd.builder() - .workchainId((byte) 0) - .address(BigInteger.ZERO) - .build()) - .dstAddr( - MsgAddressIntStd.builder() - .workchainId((byte) 0) - .address(BigInteger.ZERO) - .build()) - .value(CurrencyCollection.builder().coins(Utils.toNano(1)).build()) - .bounce(false) - .createdAt(0) - .build()) - .init(null) - .body(null) - .build(); - String internalMsgBocBase64 = internalMsg.toCell().toBase64(); - EmulateTransactionResult result = - txEmulator.emulateTransaction(shardAccountBocBase64, internalMsgBocBase64); - log.info("result {}", result); - assertThat(result.isSuccess()).isTrue(); - result.getTransaction().printTransactionFees(true, true); - - result.getTransaction().printAllMessages(true); - } - - @Test - public void testTxEmulatorEmulateTxWithAccount() { - - ShardAccount shardAccount = - ShardAccount.builder() - .account(testAccount) - .lastTransHash(BigInteger.ZERO) - .lastTransLt(BigInteger.ZERO) - .build(); - String shardAccountBocBase64 = shardAccount.toCell().toBase64(); - - Message internalMsg = - Message.builder() - .info( - InternalMessageInfo.builder() - .srcAddr( - MsgAddressIntStd.builder() - .workchainId((byte) 0) - .address(BigInteger.ZERO) - .build()) - .dstAddr( - MsgAddressIntStd.builder() - .workchainId((byte) 0) - .address(BigInteger.ZERO) - .build()) - .value(CurrencyCollection.builder().coins(Utils.toNano(1)).build()) - .bounce(false) - .createdAt(0) - .build()) - .init(null) - .body(null) - .build(); - String internalMsgBocBase64 = internalMsg.toCell().toBase64(); - EmulateTransactionResult result = - txEmulator.emulateTransaction(shardAccountBocBase64, internalMsgBocBase64); - log.info("result {}", result); - assertThat(result.isSuccess()).isTrue(); - log.info("new shardAccount {}", result.getNewShardAccount()); - log.info("new transaction {}", result.getTransaction()); - log.info("new actions {}", result.getActions()); - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - } - - @Test - public void testTxEmulatorWalletV5ExternalMsg() { - - SmartContractCompiler smcFunc = - SmartContractCompiler.builder() - .contractAsResource("/contracts/wallets/new-wallet-v5.fc") - .build(); - - String codeCellHex = smcFunc.compile(); - Cell codeCell = CellBuilder.beginCell().fromBoc(codeCellHex).endCell(); - - byte[] publicKey = - Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); - byte[] secretKey = - Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); - TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); - - WalletV5 walletV5 = - WalletV5.builder() - .keyPair(keyPair) - .isSigAuthAllowed(false) - .initialSeqno(0) - .walletId(42) - .build(); - - Cell dataCell = walletV5.createDataCell(); - - log.info("codeCellHex {}", codeCellHex); - 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()); - - Account walletV5Account = - Account.builder() - .isNone(false) - .address(MsgAddressIntStd.of(address)) - .storageInfo( - StorageInfo.builder() - .storageUsed( - StorageUsed.builder() - .cellsUsed(BigInteger.ZERO) - .bitsUsed(BigInteger.ZERO) - .publicCellsUsed(BigInteger.ZERO) - .build()) - .lastPaid(System.currentTimeMillis() / 1000) - .duePayment(BigInteger.ZERO) - .build()) - .accountStorage( - AccountStorage.builder() - .lastTransactionLt(BigInteger.ZERO) - .balance( - CurrencyCollection.builder() - .coins(Utils.toNano(5)) // initial balance - .build()) - .accountState(AccountStateActive.builder().stateInit(walletV5StateInit).build()) - .build()) - .build(); - - ShardAccount shardAccount = - ShardAccount.builder() - .account(walletV5Account) - .lastTransHash(BigInteger.ZERO) - .lastTransLt(BigInteger.ZERO) - .build(); - - String shardAccountBocBase64 = shardAccount.toCell().toBase64(); - - // txEmulator.setDebugEnabled(true); - - 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.1)) - .build())) - .toCell()) - .build(); - - Message extMsg = walletV5.prepareExternalMsg(walletV5Config); - - txEmulator.setDebugEnabled(false); - - EmulateTransactionResult result = - txEmulator.emulateTransaction(shardAccountBocBase64, extMsg.toCell().toBase64()); - - // log.info("result sendExternalMessage[1]: "+ result); - - ShardAccount newShardAccount = result.getNewShardAccount(); - // log.info("new ShardAccount "+ newShardAccount); - - TransactionDescription txDesc = result.getTransaction().getDescription(); - // log.info("txDesc "+ txDesc); - - TransactionDescriptionOrdinary txDescOrd = (TransactionDescriptionOrdinary) txDesc; - - ComputePhaseVM computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); - assertThat(computePhase.isSuccess()).isTrue(); - - ActionPhase actionPhase = txDescOrd.getActionPhase(); - assertThat(actionPhase.isSuccess()).isTrue(); - - // log.info("txDescOrd "+ txDescOrd); - assertThat(txDescOrd.isAborted()).isFalse(); - - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - - // transfer one more time - walletV5Config = - WalletV5Config.builder() - .seqno(1) - .walletId(42) - .body( - walletV5 - .createBulkTransfer( - Collections.singletonList( - Destination.builder() - .bounce(false) - .address(rawDummyDestinationAddress) - .amount(Utils.toNano(1.2)) - .build())) - .toCell()) - .build(); - - extMsg = walletV5.prepareExternalMsg(walletV5Config); - - result = txEmulator.emulateTransaction(result.getShard_account(), extMsg.toCell().toBase64()); - // log.info("result sendExternalMessage[2], exitCode: "+ result); - assertThat(result.success).isTrue(); - - newShardAccount = result.getNewShardAccount(); - // log.info("new ShardAccount "+ newShardAccount); - - txDesc = result.getTransaction().getDescription(); - // log.info("txDesc "+ txDesc); - - txDescOrd = (TransactionDescriptionOrdinary) txDesc; - - computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); - assertThat(computePhase.isSuccess()).isTrue(); - - actionPhase = txDescOrd.getActionPhase(); - assertThat(actionPhase.isSuccess()).isTrue(); - - // log.info("txDescOrd "+ txDescOrd); - assertThat(txDescOrd.isAborted()).isFalse(); - - assertThat(newShardAccount.getAccount().getAccountStorage().getBalance().getCoins()) - .isLessThan(Utils.toNano(3.2)); - assertThat(newShardAccount.getBalance()).isLessThan(Utils.toNano(3.2)); // same as above - - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - } - - @Test - public void testTxEmulatorWalletV5ExternalMsgSimplified() { - - SmartContractCompiler smcFunc = - SmartContractCompiler.builder() - .contractAsResource("/contracts/wallets/new-wallet-v5.fc") - .build(); - - Cell codeCell = smcFunc.compileToCell(); - - byte[] publicKey = - Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); - byte[] secretKey = - Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); - TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); - - WalletV5 walletV5 = - WalletV5.builder() - .keyPair(keyPair) - .isSigAuthAllowed(false) - .initialSeqno(0) - .walletId(42) - .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()) - .build(); - - Message extMsg = walletV5.prepareExternalMsg(walletV5Config); - - EmulateTransactionResult result = - txEmulator.emulateTransaction( - codeCell, dataCell, Utils.toNano(2), extMsg.toCell().toBase64()); - - // log.info("result sendExternalMessage[1]: "+ result); - // log.info("txFees: "+ result.getTransaction().getTransactionFees()); - result.getTransaction().printTransactionFees(true, true); - } - - @Test - public void testTxEmulatorWalletV5InternalMsg() throws URISyntaxException { - - URL resource = SmartContractCompiler.class.getResource("/contracts/wallets/new-wallet-v5.fc"); - String contractAbsolutePath = Paths.get(resource.toURI()).toFile().getAbsolutePath(); - SmartContractCompiler smcFunc = - SmartContractCompiler.builder().contractPath(contractAbsolutePath).build(); - - String codeCellHex = smcFunc.compile(); - Cell codeCell = CellBuilder.beginCell().fromBoc(codeCellHex).endCell(); - - byte[] publicKey = - Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); - byte[] secretKey = - Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); - TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); - - WalletV5 walletV5 = - WalletV5.builder() - .keyPair(keyPair) - .isSigAuthAllowed(false) - .initialSeqno(0) - .walletId(42) - .build(); - - Cell dataCell = walletV5.createDataCell(); - - log.info("codeCellHex {}", codeCellHex); - log.info("dataCellHex {}", dataCell.toHex()); - - Address address = StateInit.builder().code(codeCell).data(dataCell).build().getAddress(); - log.info("addressRaw {}", address.toRaw()); - log.info("addressBounceable {}", address.toBounceable()); - - Account walletV5Account = - Account.builder() - .isNone(false) - .address(MsgAddressIntStd.of(address)) - .storageInfo( - StorageInfo.builder() - .storageUsed( - StorageUsed.builder() - .cellsUsed(BigInteger.ZERO) - .bitsUsed(BigInteger.ZERO) - .publicCellsUsed(BigInteger.ZERO) - .build()) - .lastPaid(System.currentTimeMillis() / 1000) - .duePayment(BigInteger.ZERO) - .build()) - .accountStorage( - AccountStorage.builder() - .lastTransactionLt(BigInteger.ZERO) - .balance( - CurrencyCollection.builder() - .coins(Utils.toNano(5)) // initial balance - .build()) - .accountState( - AccountStateActive.builder() - .stateInit(StateInit.builder().code(codeCell).data(dataCell).build()) - .build()) - .build()) - .build(); - - ShardAccount shardAccount = - ShardAccount.builder() - .account(walletV5Account) - .lastTransHash(BigInteger.ZERO) - .lastTransLt(BigInteger.ZERO) - .build(); - String shardAccountBocBase64 = shardAccount.toCell().toBase64(); - - txEmulator.setDebugEnabled(true); - - String rawDummyDestinationAddress = - "0:258e549638a6980ae5d3c76382afd3f4f32e34482dafc3751e3358589c8de00d"; - - WalletV5Config walletV5Config = - WalletV5Config.builder() - .seqno(0) - .walletId(42) - .amount(Utils.toNano(0.1)) - .body( - walletV5 - .createBulkTransfer( - Collections.singletonList( - Destination.builder() - .bounce(false) - .address(rawDummyDestinationAddress) - .amount(Utils.toNano(1)) - .build())) - .toCell()) - .build(); - - Message intMsg = walletV5.prepareInternalMsg(walletV5Config); - - EmulateTransactionResult result = - txEmulator.emulateTransaction(shardAccountBocBase64, intMsg.toCell().toBase64()); - - log.info("result emulateTransaction: {}", result); - assertThat(result.isSuccess()).isTrue(); - - ShardAccount newShardAccount = result.getNewShardAccount(); - log.info("new ShardAccount {}", newShardAccount); - - TransactionDescription txDesc = result.getTransaction().getDescription(); - log.info("txDesc {}", txDesc); - - TransactionDescriptionOrdinary txDescOrd = (TransactionDescriptionOrdinary) txDesc; - - ComputePhaseVM computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); - assertThat(computePhase.isSuccess()).isTrue(); - - ActionPhase actionPhase = txDescOrd.getActionPhase(); - assertThat(actionPhase.isSuccess()).isTrue(); - - log.info("txDescOrd {}", txDescOrd); - assertThat(txDescOrd.isAborted()).isFalse(); - - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - - // second transfer using new shard account - - walletV5Config = - WalletV5Config.builder() - .seqno(1) - .walletId(42) - .amount(Utils.toNano(0.1)) - .body( - walletV5 - .createBulkTransfer( - Collections.singletonList( - Destination.builder() - .bounce(false) - .address(rawDummyDestinationAddress) - .amount(Utils.toNano(1)) - .build())) - .toCell()) - .build(); - - intMsg = walletV5.prepareInternalMsg(walletV5Config); - - result = - txEmulator.emulateTransaction( - newShardAccount.toCell().toBase64(), intMsg.toCell().toBase64()); - - log.info("result emulateTransaction: {}", result); - assertThat(result.success).isTrue(); - - newShardAccount = result.getNewShardAccount(); - log.info("new ShardAccount {}", newShardAccount); - - txDesc = result.getTransaction().getDescription(); - log.info("txDesc {}", txDesc); - - txDescOrd = (TransactionDescriptionOrdinary) txDesc; - - computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); - assertThat(computePhase.isSuccess()).isTrue(); - - actionPhase = txDescOrd.getActionPhase(); - assertThat(actionPhase.isSuccess()).isTrue(); - - log.info("txDescOrd {}", txDescOrd); - assertThat(txDescOrd.isAborted()).isFalse(); - - assertThat(newShardAccount.getAccount().getAccountStorage().getBalance().getCoins()) - .isLessThan(Utils.toNano(3.2)); - assertThat(newShardAccount.getBalance()).isLessThan(Utils.toNano(3.2)); // same as above + static TxEmulator txEmulator; + static Tonlib tonlib; + static Cell config; + static LiteClient liteClient; + + static Account testAccount; + + @BeforeClass + public static void setUpBeforeClass() { + liteClient = LiteClient.builder().build(); + + tonlib = Tonlib.builder().testnet(true).ignoreCache(false).build(); + + // config = tonlib.getConfigAll(128); + + txEmulator = + TxEmulator.builder() + .configType(TxEmulatorConfig.TESTNET) + .verbosityLevel(TxVerbosityLevel.TRUNCATED) + .build(); + + testAccount = + Account.builder() + .isNone(false) + .address( + MsgAddressIntStd.of( + "-1:0000000000000000000000000000000000000000000000000000000000000000")) + .storageInfo( + StorageInfo.builder() + .storageUsed( + StorageUsed.builder() + .cellsUsed(BigInteger.ZERO) + .bitsUsed(BigInteger.ZERO) + .publicCellsUsed(BigInteger.ZERO) + .build()) + .lastPaid(System.currentTimeMillis() / 1000) + .duePayment(Utils.toNano(2)) + .build()) + .accountStorage( + AccountStorage.builder() + .balance( + CurrencyCollection.builder() + .coins(Utils.toNano(2)) // initial balance + .build()) + .accountState( + AccountStateActive.builder() + .stateInit( + StateInit.builder() + .code( + CellBuilder.beginCell() + .fromBoc( + "b5ee9c7241010101004e000098ff0020dd2082014c97ba9730ed44d0d70b1fe0a4f260810200d71820d70b1fed44d0d31fd3ffd15112baf2a122f901541044f910f2a2f80001d31f31d307d4d101fb00a4c8cb1fcbffc9ed5470102286") + .endCell()) + // .data(CellBuilder.beginCell().storeBit(true).endCell()) + .build()) + .build()) + .accountStatus("ACTIVE") + .build()) + .build(); + } + + @Test + public void testInitTxEmulator() throws IOException { - result.getTransaction().printTransactionFees(true, true); - result.getTransaction().printAllMessages(true); - } - - private static Cell getLibs() { - SmcLibraryResult result = - tonlib.getLibraries( - Collections.singletonList("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M=")); - log.info("result: {}", result); - - TonHashMapE x = new TonHashMapE(256); + String configAllTestnet = + IOUtils.toString( + Objects.requireNonNull( + TestTxEmulator.class.getResourceAsStream("/config-all-testnet.txt")), + StandardCharsets.UTF_8); + + TxEmulatorI txEmulatorI = + Native.load(Utils.detectAbsolutePath("emulator", true), TxEmulatorI.class); + long emulator = txEmulatorI.transaction_emulator_create(configAllTestnet, 2); + assertNotEquals(0, emulator); + } + + @Test + public void testSetVerbosityLevel() { + txEmulator.setVerbosityLevel(4); + } + + @Test + public void testSetDebugEnabled() { + assertTrue(txEmulator.setDebugEnabled(false)); + } + + @Test + public void testCustomConfig() throws IOException { + + String configAllTestnet = + IOUtils.toString( + Objects.requireNonNull( + TestTxEmulator.class.getResourceAsStream("/config-all-testnet.txt")), + StandardCharsets.UTF_8); + + txEmulator = + TxEmulator.builder() + .configType(TxEmulatorConfig.CUSTOM) + .customConfig(configAllTestnet) + .verbosityLevel(TxVerbosityLevel.UNLIMITED) + .build(); + txEmulator.setVerbosityLevel(4); + } - for (SmcLibraryEntry l : result.getResult()) { - String cellLibBoc = l.getData(); - Cell lib = Cell.fromBocBase64(cellLibBoc); - log.info("cell lib {}", lib.toHex()); - x.elements.put(1L, lib); + @Test + public void testTxEmulatorIgnoreCheckSignature() { + assertTrue(txEmulator.setIgnoreCheckSignature(true)); } - Cell dictLibs = - x.serialize( - k -> CellBuilder.beginCell().storeUint((Long) k, 256).endCell().getBits(), - v -> CellBuilder.beginCell().storeRef((Cell) v).endCell()); - return dictLibs; - } + @Test + public void testTxEmulatorSetLt() { + assertTrue(txEmulator.setEmulatorLt(200000)); + } + + @Test + public void testTxEmulatorSetRandSeed() { + assertTrue(txEmulator.setRandSeed(Utils.sha256("ABC"))); + } + + @Test + public void testTxEmulatorSetUnixTime() { + assertTrue(txEmulator.setUnixTime(System.currentTimeMillis() / 1000)); + } + + @Test + public void testTxEmulatorSetConfig() { + assertTrue(txEmulator.setConfig(tonlib.getConfigAll(128).toBase64())); + } + + @Test + public void testTxEmulatorCreateDestroyConfig() { + String configBase64 = tonlib.getConfigAll(128).toBase64(); + long config = txEmulator.createConfig(configBase64); + txEmulator.destroyConfig(config); + } + + @Test + public void testTxEmulatorSetLibs() { + Cell dictLibs = getLibs(); + + log.info("txEmulator.setLibs() result {}", txEmulator.setLibs(dictLibs.toBase64())); + } + + @Test + public void testTxEmulatorEmulateTickTx() { + ShardAccount shardAccount = + ShardAccount.builder() + .account(testAccount) + .lastTransHash(BigInteger.valueOf(2)) + .lastTransLt(BigInteger.ZERO) + .build(); + + log.info("shardAccount: {}", shardAccount); + String shardAccountBocBase64 = shardAccount.toCell().toBase64(); + log.info("shardAccountCellBocBase64: {}", shardAccountBocBase64); + EmulateTransactionResult result = + txEmulator.emulateTickTockTransaction(shardAccountBocBase64, false); + log.info("result {}", result); + assertThat(result.success).isTrue(); + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + log.info("vm log {}", result.getVm_log()); + } + + @Test + public void testTxEmulatorEmulateTockTx() { + ShardAccount shardAccount = + ShardAccount.builder() + .account(testAccount) + .lastTransHash(BigInteger.valueOf(2)) + .lastTransLt(BigInteger.ZERO) + .build(); + + log.info("shardAccount: {}", shardAccount); + String shardAccountBocBase64 = shardAccount.toCell().toBase64(); + log.info("shardAccountCellBocBase64: {}", shardAccountBocBase64); + EmulateTransactionResult result = + txEmulator.emulateTickTockTransaction(shardAccountBocBase64, true); + log.info("result {}", result); + assertThat(result.success).isTrue(); + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + } + + @Test + public void testTxEmulatorEmulateTxWithEmptyAccount() { + + ShardAccount shardAccount = + ShardAccount.builder() + .account(Account.builder().isNone(true).build()) + .lastTransHash(BigInteger.ZERO) + .lastTransLt(BigInteger.ZERO) + .build(); + String shardAccountBocBase64 = shardAccount.toCell().toBase64(); + + Message internalMsg = + Message.builder() + .info( + InternalMessageInfo.builder() + .srcAddr( + MsgAddressIntStd.builder() + .workchainId((byte) 0) + .address(BigInteger.ZERO) + .build()) + .dstAddr( + MsgAddressIntStd.builder() + .workchainId((byte) 0) + .address(BigInteger.ZERO) + .build()) + .value(CurrencyCollection.builder().coins(Utils.toNano(1)).build()) + .bounce(false) + .createdAt(0) + .build()) + .init(null) + .body(null) + .build(); + String internalMsgBocBase64 = internalMsg.toCell().toBase64(); + EmulateTransactionResult result = + txEmulator.emulateTransaction(shardAccountBocBase64, internalMsgBocBase64); + log.info("result {}", result); + assertThat(result.isSuccess()).isTrue(); + result.getTransaction().printTransactionInfo(true, true); + + result.getTransaction().printAllMessages(true); + } + + @Test + public void testTxEmulatorEmulateTxWithAccount() { + + ShardAccount shardAccount = + ShardAccount.builder() + .account(testAccount) + .lastTransHash(BigInteger.ZERO) + .lastTransLt(BigInteger.ZERO) + .build(); + String shardAccountBocBase64 = shardAccount.toCell().toBase64(); + + Message internalMsg = + Message.builder() + .info( + InternalMessageInfo.builder() + .srcAddr( + MsgAddressIntStd.builder() + .workchainId((byte) 0) + .address(BigInteger.ZERO) + .build()) + .dstAddr( + MsgAddressIntStd.builder() + .workchainId((byte) 0) + .address(BigInteger.ZERO) + .build()) + .value(CurrencyCollection.builder().coins(Utils.toNano(1)).build()) + .bounce(false) + .createdAt(0) + .build()) + .init(null) + .body(null) + .build(); + String internalMsgBocBase64 = internalMsg.toCell().toBase64(); + EmulateTransactionResult result = + txEmulator.emulateTransaction(shardAccountBocBase64, internalMsgBocBase64); + log.info("result {}", result); + assertThat(result.isSuccess()).isTrue(); + log.info("new shardAccount {}", result.getNewShardAccount()); + log.info("new transaction {}", result.getTransaction()); + log.info("new actions {}", result.getActions()); + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + } + + @Test + public void testTxEmulatorWalletV5ExternalMsg() { + + SmartContractCompiler smcFunc = + SmartContractCompiler.builder() + .contractAsResource("/contracts/wallets/new-wallet-v5.fc") + .build(); + + String codeCellHex = smcFunc.compile(); + Cell codeCell = CellBuilder.beginCell().fromBoc(codeCellHex).endCell(); + + byte[] publicKey = + Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); + byte[] secretKey = + Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); + TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); + + WalletV5 walletV5 = + WalletV5.builder() + .keyPair(keyPair) + .isSigAuthAllowed(false) + .initialSeqno(0) + .walletId(42) + .build(); + + Cell dataCell = walletV5.createDataCell(); + + log.info("codeCellHex {}", codeCellHex); + 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()); + + Account walletV5Account = + Account.builder() + .isNone(false) + .address(MsgAddressIntStd.of(address)) + .storageInfo( + StorageInfo.builder() + .storageUsed( + StorageUsed.builder() + .cellsUsed(BigInteger.ZERO) + .bitsUsed(BigInteger.ZERO) + .publicCellsUsed(BigInteger.ZERO) + .build()) + .lastPaid(System.currentTimeMillis() / 1000) + .duePayment(BigInteger.ZERO) + .build()) + .accountStorage( + AccountStorage.builder() + .lastTransactionLt(BigInteger.ZERO) + .balance( + CurrencyCollection.builder() + .coins(Utils.toNano(5)) // initial balance + .build()) + .accountState(AccountStateActive.builder().stateInit(walletV5StateInit).build()) + .build()) + .build(); + + ShardAccount shardAccount = + ShardAccount.builder() + .account(walletV5Account) + .lastTransHash(BigInteger.ZERO) + .lastTransLt(BigInteger.ZERO) + .build(); + + String shardAccountBocBase64 = shardAccount.toCell().toBase64(); + + // txEmulator.setDebugEnabled(true); + + 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.1)) + .build())) + .toCell()) + .build(); + + Message extMsg = walletV5.prepareExternalMsg(walletV5Config); + + txEmulator.setDebugEnabled(false); + + EmulateTransactionResult result = + txEmulator.emulateTransaction(shardAccountBocBase64, extMsg.toCell().toBase64()); + + // log.info("result sendExternalMessage[1]: "+ result); + + ShardAccount newShardAccount = result.getNewShardAccount(); + // log.info("new ShardAccount "+ newShardAccount); + + TransactionDescription txDesc = result.getTransaction().getDescription(); + // log.info("txDesc "+ txDesc); + + TransactionDescriptionOrdinary txDescOrd = (TransactionDescriptionOrdinary) txDesc; + + ComputePhaseVM computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); + assertThat(computePhase.isSuccess()).isTrue(); + + ActionPhase actionPhase = txDescOrd.getActionPhase(); + assertThat(actionPhase.isSuccess()).isTrue(); + + // log.info("txDescOrd "+ txDescOrd); + assertThat(txDescOrd.isAborted()).isFalse(); + + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + + // transfer one more time + walletV5Config = + WalletV5Config.builder() + .seqno(1) + .walletId(42) + .body( + walletV5 + .createBulkTransfer( + Collections.singletonList( + Destination.builder() + .bounce(false) + .address(rawDummyDestinationAddress) + .amount(Utils.toNano(1.2)) + .build())) + .toCell()) + .build(); + + extMsg = walletV5.prepareExternalMsg(walletV5Config); + + result = txEmulator.emulateTransaction(result.getShard_account(), extMsg.toCell().toBase64()); + // log.info("result sendExternalMessage[2], exitCode: "+ result); + assertThat(result.success).isTrue(); + + newShardAccount = result.getNewShardAccount(); + // log.info("new ShardAccount "+ newShardAccount); + + txDesc = result.getTransaction().getDescription(); + // log.info("txDesc "+ txDesc); + + txDescOrd = (TransactionDescriptionOrdinary) txDesc; + + computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); + assertThat(computePhase.isSuccess()).isTrue(); + + actionPhase = txDescOrd.getActionPhase(); + assertThat(actionPhase.isSuccess()).isTrue(); + + // log.info("txDescOrd "+ txDescOrd); + assertThat(txDescOrd.isAborted()).isFalse(); + + assertThat(newShardAccount.getAccount().getAccountStorage().getBalance().getCoins()) + .isLessThan(Utils.toNano(3.2)); + assertThat(newShardAccount.getBalance()).isLessThan(Utils.toNano(3.2)); // same as above + + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + } + + @Test + public void testTxEmulatorWalletV5ExternalMsgSimplified() { + + SmartContractCompiler smcFunc = + SmartContractCompiler.builder() + .contractAsResource("/contracts/wallets/new-wallet-v5.fc") + .build(); + + Cell codeCell = smcFunc.compileToCell(); + + byte[] publicKey = + Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); + byte[] secretKey = + Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); + TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); + + WalletV5 walletV5 = + WalletV5.builder() + .keyPair(keyPair) + .isSigAuthAllowed(false) + .initialSeqno(0) + .walletId(42) + .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()) + .build(); + + Message extMsg = walletV5.prepareExternalMsg(walletV5Config); + + EmulateTransactionResult result = + txEmulator.emulateTransaction( + codeCell, dataCell, Utils.toNano(2), extMsg.toCell().toBase64()); + + // log.info("result sendExternalMessage[1]: "+ result); + // log.info("txFees: "+ result.getTransaction().getTransactionFees()); + result.getTransaction().printTransactionInfo(true, true); + } + + @Test + public void testTxEmulatorWalletV5InternalMsg() throws URISyntaxException { + + URL resource = SmartContractCompiler.class.getResource("/contracts/wallets/new-wallet-v5.fc"); + String contractAbsolutePath = Paths.get(resource.toURI()).toFile().getAbsolutePath(); + SmartContractCompiler smcFunc = + SmartContractCompiler.builder().contractPath(contractAbsolutePath).build(); + + String codeCellHex = smcFunc.compile(); + Cell codeCell = CellBuilder.beginCell().fromBoc(codeCellHex).endCell(); + + byte[] publicKey = + Utils.hexToSignedBytes("82A0B2543D06FEC0AAC952E9EC738BE56AB1B6027FC0C1AA817AE14B4D1ED2FB"); + byte[] secretKey = + Utils.hexToSignedBytes("F182111193F30D79D517F2339A1BA7C25FDF6C52142F0F2C1D960A1F1D65E1E4"); + TweetNaclFast.Signature.KeyPair keyPair = TweetNaclFast.Signature.keyPair_fromSeed(secretKey); + + WalletV5 walletV5 = + WalletV5.builder() + .keyPair(keyPair) + .isSigAuthAllowed(false) + .initialSeqno(0) + .walletId(42) + .build(); + + Cell dataCell = walletV5.createDataCell(); + + log.info("codeCellHex {}", codeCellHex); + log.info("dataCellHex {}", dataCell.toHex()); + + Address address = StateInit.builder().code(codeCell).data(dataCell).build().getAddress(); + log.info("addressRaw {}", address.toRaw()); + log.info("addressBounceable {}", address.toBounceable()); + + Account walletV5Account = + Account.builder() + .isNone(false) + .address(MsgAddressIntStd.of(address)) + .storageInfo( + StorageInfo.builder() + .storageUsed( + StorageUsed.builder() + .cellsUsed(BigInteger.ZERO) + .bitsUsed(BigInteger.ZERO) + .publicCellsUsed(BigInteger.ZERO) + .build()) + .lastPaid(System.currentTimeMillis() / 1000) + .duePayment(BigInteger.ZERO) + .build()) + .accountStorage( + AccountStorage.builder() + .lastTransactionLt(BigInteger.ZERO) + .balance( + CurrencyCollection.builder() + .coins(Utils.toNano(5)) // initial balance + .build()) + .accountState( + AccountStateActive.builder() + .stateInit(StateInit.builder().code(codeCell).data(dataCell).build()) + .build()) + .build()) + .build(); + + ShardAccount shardAccount = + ShardAccount.builder() + .account(walletV5Account) + .lastTransHash(BigInteger.ZERO) + .lastTransLt(BigInteger.ZERO) + .build(); + String shardAccountBocBase64 = shardAccount.toCell().toBase64(); + + txEmulator.setDebugEnabled(true); + + String rawDummyDestinationAddress = + "0:258e549638a6980ae5d3c76382afd3f4f32e34482dafc3751e3358589c8de00d"; + + WalletV5Config walletV5Config = + WalletV5Config.builder() + .seqno(0) + .walletId(42) + .amount(Utils.toNano(0.1)) + .body( + walletV5 + .createBulkTransfer( + Collections.singletonList( + Destination.builder() + .bounce(false) + .address(rawDummyDestinationAddress) + .amount(Utils.toNano(1)) + .build())) + .toCell()) + .build(); + + Message intMsg = walletV5.prepareInternalMsg(walletV5Config); + + EmulateTransactionResult result = + txEmulator.emulateTransaction(shardAccountBocBase64, intMsg.toCell().toBase64()); + + log.info("result emulateTransaction: {}", result); + assertThat(result.isSuccess()).isTrue(); + + ShardAccount newShardAccount = result.getNewShardAccount(); + log.info("new ShardAccount {}", newShardAccount); + + TransactionDescription txDesc = result.getTransaction().getDescription(); + log.info("txDesc {}", txDesc); + + TransactionDescriptionOrdinary txDescOrd = (TransactionDescriptionOrdinary) txDesc; + + ComputePhaseVM computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); + assertThat(computePhase.isSuccess()).isTrue(); + + ActionPhase actionPhase = txDescOrd.getActionPhase(); + assertThat(actionPhase.isSuccess()).isTrue(); + + log.info("txDescOrd {}", txDescOrd); + assertThat(txDescOrd.isAborted()).isFalse(); + + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + + // second transfer using new shard account + + walletV5Config = + WalletV5Config.builder() + .seqno(1) + .walletId(42) + .amount(Utils.toNano(0.1)) + .body( + walletV5 + .createBulkTransfer( + Collections.singletonList( + Destination.builder() + .bounce(false) + .address(rawDummyDestinationAddress) + .amount(Utils.toNano(1)) + .build())) + .toCell()) + .build(); + + intMsg = walletV5.prepareInternalMsg(walletV5Config); + + result = + txEmulator.emulateTransaction( + newShardAccount.toCell().toBase64(), intMsg.toCell().toBase64()); + + log.info("result emulateTransaction: {}", result); + assertThat(result.success).isTrue(); + + newShardAccount = result.getNewShardAccount(); + log.info("new ShardAccount {}", newShardAccount); + + txDesc = result.getTransaction().getDescription(); + log.info("txDesc {}", txDesc); + + txDescOrd = (TransactionDescriptionOrdinary) txDesc; + + computePhase = (ComputePhaseVM) txDescOrd.getComputePhase(); + assertThat(computePhase.isSuccess()).isTrue(); + + actionPhase = txDescOrd.getActionPhase(); + assertThat(actionPhase.isSuccess()).isTrue(); + + log.info("txDescOrd {}", txDescOrd); + assertThat(txDescOrd.isAborted()).isFalse(); + + assertThat(newShardAccount.getAccount().getAccountStorage().getBalance().getCoins()) + .isLessThan(Utils.toNano(3.2)); + assertThat(newShardAccount.getBalance()).isLessThan(Utils.toNano(3.2)); // same as above + + result.getTransaction().printTransactionInfo(true, true); + result.getTransaction().printAllMessages(true); + } + + private static Cell getLibs() { + SmcLibraryResult result = + tonlib.getLibraries( + Collections.singletonList("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M=")); + log.info("result: {}", result); + + TonHashMapE x = new TonHashMapE(256); + + for (SmcLibraryEntry l : result.getResult()) { + String cellLibBoc = l.getData(); + Cell lib = Cell.fromBocBase64(cellLibBoc); + log.info("cell lib {}", lib.toHex()); + x.elements.put(1L, lib); + } + + Cell dictLibs = + x.serialize( + k -> CellBuilder.beginCell().storeUint((Long) k, 256).endCell().getBits(), + v -> CellBuilder.beginCell().storeRef((Cell) v).endCell()); + return dictLibs; + } } diff --git a/liteclient/pom.xml b/liteclient/pom.xml index 319aec12..c4eb3977 100644 --- a/liteclient/pom.xml +++ b/liteclient/pom.xml @@ -49,6 +49,12 @@ ${project.parent.version} compile + + io.github.neodix42 + cell + ${project.parent.version} + compile + org.apache.commons commons-lang3 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 dd4f2d4b..1482ccac 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/LiteClient.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/LiteClient.java @@ -1,7 +1,17 @@ package org.ton.java.liteclient; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; +import lombok.Builder; +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.liteclient.api.block.Block; +import org.ton.java.liteclient.api.block.Transaction; +import org.ton.java.utils.Utils; import java.io.File; import java.io.IOException; @@ -15,503 +25,646 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import lombok.Builder; -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; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; @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; - /** - * 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; - private int timeout; + private String nodeName; - private String nodeName; + /** + * Ignored if pathToGlobalConfig is not null. + */ + private boolean testnet; - /** Ignored if pathToGlobalConfig is not null. */ - private boolean testnet; + private Boolean printInfo; - public static class LiteClientBuilder {} + public static class LiteClientBuilder { + } - public static LiteClientBuilder builder() { - return new CustomLiteClientBuilder(); - } + public static LiteClientBuilder builder() { + return new CustomLiteClientBuilder(); + } - private static class CustomLiteClientBuilder extends LiteClientBuilder { - @Override - public LiteClient build() { + private static class CustomLiteClientBuilder extends LiteClientBuilder { + @Override + public LiteClient build() { - try { + try { - if (StringUtils.isEmpty(super.pathToLiteClientBinary)) { - log.info("checking if lite-client is installed..."); - String errorMsg = - "Make sure you have lite-client installed. See https://github.com/ton-blockchain/packages for instructions.\nYou can also specify full path via LiteClientBuilder.pathToLiteClientBinary()."; - 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); - } + if (isNull(super.printInfo)) { + super.printInfo = true; + } + + if (StringUtils.isEmpty(super.pathToLiteClientBinary)) { + if (super.printInfo) { + log.info("checking if lite-client is installed..."); + } + String errorMsg = + "Make sure you have lite-client installed. See https://github.com/ton-blockchain/packages for instructions.\nYou can also specify full path via LiteClientBuilder.pathToLiteClientBinary()."; + 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); + if (super.printInfo) { + log.info("lite-client found at " + super.pathToLiteClientBinary); + } + + } catch (Exception e) { + throw new Error("Cannot execute simple lite-client command.\n" + errorMsg); + } + } + + 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); + } + } + + if (super.printInfo) { + 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; } + } - if (super.timeout == 0) { - super.timeout = 10; + public long executeGetSeqno(String contractAddress) { + try { + return LiteClientParser.parseRunMethodSeqno(executeRunMethod(contractAddress, "seqno", "")); + } catch (Exception e) { + return -1L; } + } - if (isNull(super.pathToGlobalConfig)) { + public long executeGetSubWalletId(String contractAddress) { + try { + return LiteClientParser.parseRunMethodSeqno( + executeRunMethod(contractAddress, "get_subwallet_id", "")); + } catch (Exception e) { + return -1L; + } + } - 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(); - } + /** + * @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; + } + } - config.close(); + /** + * @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 { - if (!Files.exists(Paths.get(super.pathToGlobalConfig))) { - throw new RuntimeException( - "Global config is not found in path: " + super.pathToGlobalConfig); - } + return null; } + } - 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); - - String resultInput = - IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); - - return resultInput; - - } catch (IOException e) { - log.info(e.getMessage()); - 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; + } + } + + /** + * executes 2 calls against lite-server + * + *

+     * 1. last
+     * 2. dumpblock
+     * 
+ * + * @return List + */ + public List getAllTransactionsFromLatestBlock() { + try { + Block lastBlock = + LiteClientParser.parseDumpblock( + executeDumpblock(LiteClientParser.parseLast(executeLast())), false, false); + return lastBlock.listBlockTrans(); + } catch (Exception e) { + throw new Error("Cannot retrieve all transactions from last block"); + } + } + + public List getAllTransactionsByBlock(ResultLastBlock block) { + try { + Block lastBlock = LiteClientParser.parseDumpblock(executeDumpblock(block), false, true); + return lastBlock.listBlockTrans(); + } catch (Exception e) { + throw new Error("Cannot retrieve all transactions from block"); + } + } + + public List getAccountTransactionsByBlock(ResultLastBlock block, String address) { + try { + Block lastBlock = LiteClientParser.parseDumpblock(executeDumpblock(block), false, true); + return lastBlock.listBlockTrans(address); + } catch (Exception e) { + throw new Error("Cannot retrieve all transactions from block"); + } + } + + /** + * executes 3+x calls against lite-server + * + *
+     * 1. last
+     * 2. dumpblock latest master chain
+     * 3. allshards
+     * 4. dumpblock latest block for each shard
+     * 
+ * + * @return List + */ + public List getAllTransactionsFromLatestBlockAndAllShards() { + try { + List txs; + ResultLastBlock blockIdLast = LiteClientParser.parseLast(executeLast()); + + txs = getAllTransactionsByBlock(blockIdLast); + + List shards = LiteClientParser.parseAllShards(executeAllshards(blockIdLast)); + for (ResultLastBlock shard : shards) { + List shardTxs = getAllTransactionsByBlock(shard); + txs.addAll(shardTxs); + } + return txs; + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Cannot retrieve all transactions from all shards"); + } + } + + /** + * executes 3+x calls against lite-server + * + *
+     * 1. last
+     * 2. dumpblock latest master chain
+     * 3. allshards
+     * 4. dumpblock latest block for each shard
+     * 
+ * + * @return List + */ + public List getAccountTransactionsFromLatestBlockAndAllShards(String address) { + try { + List txs; + ResultLastBlock blockIdLast = LiteClientParser.parseLast(executeLast()); + + txs = getAccountTransactionsByBlock(blockIdLast, address); + + List shards = LiteClientParser.parseAllShards(executeAllshards(blockIdLast)); + for (ResultLastBlock shard : shards) { + List shardTxs = getAccountTransactionsByBlock(shard, address); + txs.addAll(shardTxs); + } + return txs; + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Cannot retrieve all transactions from all shards"); + } + } + + /** + * executes 3+x calls against lite-server + * + *
+     * 1. last
+     * 2. dumpblock latest master chain
+     * 3. allshards
+     * 4. dumpblock latest block for each shard
+     * 
+ * + * @return List + */ + public List getAccountTransactionsFromBlockAndAllShards( + ResultLastBlock blockId, String address) { + try { + List txs; + + txs = getAccountTransactionsByBlock(blockId, address); + + List shards = LiteClientParser.parseAllShards(executeAllshards(blockId)); + for (ResultLastBlock shard : shards) { + List shardTxs = getAccountTransactionsByBlock(shard, address); + txs.addAll(shardTxs); + } + return txs; + } catch (Exception e) { + e.printStackTrace(); + throw new Error("Cannot retrieve all transactions from all shards"); + } + } + + 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; + } + } - executorService.shutdown(); + // 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; + } + } - return Pair.of(p, future); + 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); + + String resultInput = + IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + + return resultInput; + + } catch (IOException e) { + log.info(e.getMessage()); + return null; + } + }); + + executorService.shutdown(); + + return Pair.of(p, future); + + } catch (final IOException | InterruptedException e) { + log.info(e.getMessage()); + return null; + } + } - } catch (final IOException | InterruptedException e) { - log.info(e.getMessage()); - return null; + public String getLiteClientPath() { + return Utils.detectAbsolutePath("lite-client", false); } - } } 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 1072912e..266f991c 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/LiteClientParser.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/LiteClientParser.java @@ -1,1718 +1,1741 @@ package org.ton.java.liteclient; -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.*; import org.ton.java.liteclient.api.block.Currency; +import org.ton.java.liteclient.api.block.*; 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; + @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; - } + 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, "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"); - } + 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; + 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 ResultLastBlock parseCreateHardFork(String stdout) { + public static ResultLastBlock parseCreateHardFork(String stdout) { - if (StringUtils.isEmpty(stdout)) { - log.info("parseCreateHardfork, stdout is empty: {}", stdout); - return null; - } + 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; - } - } - - 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); + 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; + } } - } - public static List parseListBlockTrans(String stdout) { + public static ResultLastBlock parseBySeqno(String stdout) throws IncompleteDump, ParsingError { - if (StringUtils.isEmpty(stdout) || !stdout.contains(TRANSACTION_TAG)) - return Collections.emptyList(); + if (StringUtils.isEmpty(stdout) + || stdout.contains("seqno not in db") + || stdout.contains("block not found")) + throw new IncompleteDump("parseBySeqno: block is missing"); - String onlyTransactions = stdout.substring(stdout.indexOf(TRANSACTION_TAG)); + if (!stdout.contains("global_id")) + throw new IncompleteDump("parseBySeqno: incomplete dump or block missing"); + try { - String[] lines = onlyTransactions.split("\\r?\\n"); + String last = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - List txs = new ArrayList<>(); + 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(); - 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; - } + String shard = sb(shortBlockSeqno, ",", ","); + BigInteger pureBlockSeqno = new BigInteger(sb(shortBlockSeqno, shard + ",", CLOSE)); + Long wc = Long.parseLong(sb(shortBlockSeqno, OPEN, ",")); - 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()); + 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); + } } - return validators; - } + public static List parseListBlockTrans(String stdout) { + + if (StringUtils.isEmpty(stdout) || !stdout.contains(TRANSACTION_TAG)) + return Collections.emptyList(); - /** current validators */ - public static ResultConfig34 parseConfig34(String stdout) { + String onlyTransactions = stdout.substring(stdout.indexOf(TRANSACTION_TAG)); - stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + String[] lines = onlyTransactions.split("\\r?\\n"); - List validators = new ArrayList<>(); + List txs = 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(); + 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; } - 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(); + 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); } - 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(); + // config address + public static ResultConfig0 parseConfig0(String stdout) { + return ResultConfig0.builder() + .configSmcAddr("-1:" + sb(stdout, "config_addr:x", CLOSE)) + .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); + // elector address + public static ResultConfig1 parseConfig1(String stdout) { + return ResultConfig1.builder() + .electorSmcAddress("-1:" + sb(stdout, "elector_addr:x", CLOSE)) + .build(); } - } - public static Block parseDumpblock( - String stdout, boolean includeShardState, boolean includeMessageBody) - throws IncompleteDump, ParsingError { + // minter address + public static ResultConfig2 parseConfig2(String stdout) { + return ResultConfig2.builder() + .minterSmcAddress("-1:" + sb(stdout, "minter_addr:x", CLOSE)) + .build(); + } - if (StringUtils.isEmpty(stdout) || stdout.length() < 400) - throw new IncompleteDump("parseDumpblock: incomplete dump"); + public static ResultConfig12 parseConfig12(String stdout) { - try { - String blockdump = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - Long globalBlockId = Long.parseLong(sb(blockdump, "block global_id:", 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(); + } - String blockInf = sbb(blockdump, "info:(block_info"); - String valueFlw = sbb(blockdump, "value_flow:(value_flow"); + public static ResultConfig15 parseConfig15(String stdout) { - String shardState = null; - if (includeShardState) { - shardState = sbb(blockdump, "state_update:(raw@(MERKLE_UPDATE"); - } + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - String blockExtra = sbb(blockdump, "extra:(block_extra"); - Info info = parseBlockInfo(blockInf); - ValueFlow valueFlow = parseValueFlow(valueFlw); - Extra extra = parseExtra(blockExtra, includeMessageBody); + // 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(); + } - return Block.builder() - .globalId(globalBlockId) - .info(info) - .valueFlow(valueFlow) - .shardState(shardState) - .extra(extra) - .build(); + public static ResultConfig17 parseConfig17(String stdout) { - } catch (Exception e) { - throw new ParsingError("parseDumpblock: parsing error", e); - } - } + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - private static Extra parseExtra(String blockExtra, Boolean includeMessageBody) { + String minStake = sbb(stdout, "min_stake:"); + String maxStake = sbb(stdout, "max_stake:"); + String minTotalStake = sbb(stdout, "min_total_stake:"); - 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"); + return ResultConfig17.builder() + .minStake(parseBigIntegerBracket(minStake, "value:")) + .maxStake(parseBigIntegerBracket(maxStake, "value:")) + .minTotalStake(parseBigIntegerBracket(minTotalStake, "value:")) + .maxStakeFactor(parseBigIntegerBracket(stdout, "max_stake_factor:")) + .build(); + } - InMsgDescr inMsgDescr = parseInMsgDescr(inMsgDesc, includeMessageBody); + private static List parseConfigValidators(String stdout) { + List validators = new ArrayList<>(); - OutMsgDescr outMsgDescr = parseOutMsgDescr(outMsgDesc, includeMessageBody); + List unparsedLeafs = findStringBlocks(stdout, "node:(hmn_leaf"); - 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); - } + for (String leaf : unparsedLeafs) { - 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; - } + String pubKey = sb(leaf, "pubkey:x", CLOSE); + BigInteger weight; + String adnlAddress; - private static OutMsgDescr parseOutMsgDescr(String outMsgDescr, Boolean includeMessageBody) { - List unparsedLeafs = findLeafsWithLabel(outMsgDescr, "node:(ahmn_leaf"); + 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()); + } - List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); + return validators; + } - return OutMsgDescr.builder().leaf(parsedLeafs).build(); - } + /** + * current validators + */ + public static ResultConfig34 parseConfig34(String stdout) { - private static InMsgDescr parseInMsgDescr(String inMsgDesc, boolean includeMessageBody) { + stdout = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - List unparsedLeafs = findLeafsWithLabel(inMsgDesc, "node:(ahmn_leaf"); + List validators = new ArrayList<>(); - List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); + 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(); + } - return InMsgDescr.builder().leaf(parsedLeafs).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(); + } - 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")); + 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(); + } - Leaf leaf = - Leaf.builder() - .label(label) - .message(inMsg) - .transactions(txs) - .feesCollected(feesCollected) - .valueImported(valueImported) - .build(); - result.add(leaf); - } - 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"); - } - 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; + 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); + } } - } - 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); + public static Block parseDumpblock( + String stdout, boolean includeShardState, boolean includeMessageBody) + throws IncompleteDump, ParsingError { - return Body.builder().cells(cleanedCells).build(); - } + if (StringUtils.isEmpty(stdout) || stdout.length() < 400) + throw new IncompleteDump("parseDumpblock: incomplete dump"); - private static List cleanCells(List cells) { - if (cells == null || cells.isEmpty()) { - return new ArrayList<>(); - } + try { + String blockdump = stdout.replace(EOLWIN, SPACE).replace(EOL, SPACE); - 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; - } + Long globalBlockId = Long.parseLong(sb(blockdump, "block global_id:", SPACE)); - while (outMsgsField.contains("(message")) { + String blockInf = sbb(blockdump, "info:(block_info"); + String valueFlw = sbb(blockdump, "value_flow:(value_flow"); - String message = sbb(outMsgsField, "(message"); + String shardState = null; + if (includeShardState) { + shardState = sbb(blockdump, "state_update:(raw@(MERKLE_UPDATE"); + } - 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 blockExtra = sbb(blockdump, "extra:(block_extra"); + Info info = parseBlockInfo(blockInf); + ValueFlow valueFlow = parseValueFlow(valueFlw); + Extra extra = parseExtra(blockExtra, includeMessageBody); - 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(); + return Block.builder() + .globalId(globalBlockId) + .info(info) + .valueFlow(valueFlow) + .shardState(shardState) + .extra(extra) + .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); + } catch (Exception e) { + throw new ParsingError("parseDumpblock: parsing error", e); } - LiteClientAddress destinationAddr = - LiteClientAddress.builder().wc(destWc).addr(destAddr).build(); + } - Value toncoins = readValue(sbb(message, "value:(currencies")); + private static Extra parseExtra(String blockExtra, Boolean includeMessageBody) { - 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 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 initContent = sbb(message, "init:("); - Init init = parseInit(initContent); + InMsgDescr inMsgDescr = parseInMsgDescr(inMsgDesc, includeMessageBody); - Body body = null; - if (includeMessageBody) { - String bodyContent = sbb(message, "body:("); - body = parseBody(bodyContent); + 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); } - 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"; + 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 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 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 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 OutMsgDescr parseOutMsgDescr(String outMsgDescr, Boolean includeMessageBody) { + List unparsedLeafs = findLeafsWithLabel(outMsgDescr, "node:(ahmn_leaf"); + + List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); + + return OutMsgDescr.builder().leaf(parsedLeafs).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 InMsgDescr parseInMsgDescr(String inMsgDesc, boolean includeMessageBody) { + + List unparsedLeafs = findLeafsWithLabel(inMsgDesc, "node:(ahmn_leaf"); + + List parsedLeafs = parseLeafs(unparsedLeafs, includeMessageBody); + + return InMsgDescr.builder().leaf(parsedLeafs).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 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; } - } - - 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 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; + } } - } - - 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 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(); } - } - - 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 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 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); - } - } - } + + 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(); } - return Value.builder().toncoins(grams).otherCurrencies(otherCurrencies).build(); - } + private static List parseOutMessages(String outMsgsField, boolean includeMessageBody) { - private static List readLibraries(String str) { - if (StringUtils.isEmpty(str)) { - return Collections.singletonList(Library.builder().build()); + List outMsgs = new ArrayList<>(); + if (StringUtils.isEmpty(outMsgsField)) { + return outMsgs; + } + + 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"; + } } - 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 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; + } } - 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 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"; + } + 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"; + } + + 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(); + } } - String prevFileHash = sb(prev, "file_hash:", CLOSE); - if (Strings.isNotEmpty(prevFileHash)) { - prevFileHash = prevFileHash.substring(1); + + 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 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 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 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 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 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 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(); } - } - private static Long parseLongBracket(String str, String from) { - if (isNull(str)) { - return 0L; + private static List readLibraries(String str) { + if (StringUtils.isEmpty(str)) { + return Collections.singletonList(Library.builder().build()); + } + + 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 allLibraries; + } + + private static Info parseBlockInfo(String blockInf) { + + BigInteger version = parseBigIntegerSpace(blockInf, "version:"); + Byte notMaster = parseByteSpace(blockInf, "not_master:"); + String shard = sbb(blockInf, "shard:("); + BigInteger shardPrefix = new BigInteger(sb(shard, "shard_prefix:", CLOSE)); + BigInteger shardPrefixBits = parseBigIntegerSpace(blockInf, "shard_pfx_bits:"); + Long shardWorkchain = parseLongSpace(blockInf, "workchain_id:"); + 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) + .shardPrefix(shardPrefix) + .shardWorkchain(shardWorkchain) + .shardPrefixBits(shardPrefixBits.intValue()) + .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; + } } - 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; + + 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 BigDecimal parseBigDecimalBracket(String str, String from) { - if (isNull(str)) { - return BigDecimal.ZERO; + 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; + } } - 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; + + 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; + } } - } - private static BigInteger parseBigIntegerBracket(String str, String from) { - if (isNull(str)) { - return BigInteger.ZERO; + 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; + } } - 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 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; + + 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 Byte parseByteBracket(String str, String from) { - String result = sb(str, from, CLOSE); - return isNull(result) ? null : Byte.parseByte(result); - } + private static Byte parseByteBracket(String str, String from) { + String result = sb(str, from, CLOSE); + return isNull(result) ? null : Byte.parseByte(result); + } - public static LiteClientAccountState parseGetAccount(String stdout) { + public static LiteClientAccountState parseGetAccount(String stdout) { - if (isNull(stdout)) { - return LiteClientAccountState.builder().build(); - } + if (isNull(stdout)) { + return LiteClientAccountState.builder().build(); + } - if (stdout.contains("account state is empty")) { - 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(); + 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(); + } } - } - public static long parseRunMethodSeqno(String stdout) { - try { - return Long.parseLong(sb(stdout, "result: [", "]").trim()); - } catch (Exception e) { - return -1L; + public static long parseRunMethodSeqno(String stdout) { + try { + return Long.parseLong(sb(stdout, "result: [", "]").trim()); + } catch (Exception e) { + return -1L; + } } - } - public static List parseRunMethodParticipantList(String stdout) { + public static List parseRunMethodParticipantList(String stdout) { - if (StringUtils.isEmpty(stdout) || !stdout.contains("participant_list")) - return Collections.emptyList(); + if (StringUtils.isEmpty(stdout) || !stdout.contains("participant_list")) + return Collections.emptyList(); - if (stdout.contains("cannot parse answer")) { - return Collections.emptyList(); - } + if (stdout.contains("cannot parse answer")) { + return Collections.emptyList(); + } - String result = sb(stdout, "result: [ (", ") ]"); + String result = sb(stdout, "result: [ (", ") ]"); - result = result.replace("] [", ","); - result = result.replace("[", ""); - result = result.replace("]", ""); - String[] participants = result.split(","); + result = result.replace("] [", ","); + result = result.replace("[", ""); + result = result.replace("]", ""); + String[] participants = result.split(","); - 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()); + 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; } - return participantsList; - } - public static ResultComputeReturnStake parseRunMethodComputeReturnStake(String stdout) { + public static ResultComputeReturnStake parseRunMethodComputeReturnStake(String stdout) { - log.info("parseRunMethodComputeReturnStake {}", stdout); + log.info("parseRunMethodComputeReturnStake {}", stdout); + + if (StringUtils.isEmpty(stdout) || !stdout.contains("compute_returned_stake")) + return ResultComputeReturnStake.builder().stake(new BigDecimal("-1")).build(); + + if (stdout.contains("cannot parse answer")) { + return ResultComputeReturnStake.builder().stake(new BigDecimal("-1")).build(); + } - if (StringUtils.isEmpty(stdout) || !stdout.contains("compute_returned_stake")) - return ResultComputeReturnStake.builder().stake(new BigDecimal("-1")).build(); + String result = sb(stdout, "result: [", "]"); - if (stdout.contains("cannot parse answer")) { - return ResultComputeReturnStake.builder().stake(new BigDecimal("-1")).build(); + return ResultComputeReturnStake.builder().stake(new BigDecimal(result.trim())).build(); } - String result = sb(stdout, "result: [", "]"); + private static String sb(String str, String from, String to) { + if (str == null || from == null || to == null) { + return null; + } - return ResultComputeReturnStake.builder().stake(new BigDecimal(result.trim())).build(); - } + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + byte[] fromBytes = from.getBytes(StandardCharsets.UTF_8); + byte[] toBytes = to.getBytes(StandardCharsets.UTF_8); - private static String sb(String str, String from, String to) { - if (str == null || from == null || to == null) { - return null; - } + int startIndex = indexOf(bytes, fromBytes, 0); + if (startIndex == -1) { + return null; + } + startIndex += fromBytes.length; - byte[] bytes = str.getBytes(StandardCharsets.UTF_8); - byte[] fromBytes = from.getBytes(StandardCharsets.UTF_8); - byte[] toBytes = to.getBytes(StandardCharsets.UTF_8); + int endIndex = indexOf(bytes, toBytes, startIndex); + if (endIndex == -1) { + return null; + } - int startIndex = indexOf(bytes, fromBytes, 0); - if (startIndex == -1) { - return null; + byte[] resultBytes = Arrays.copyOfRange(bytes, startIndex, endIndex); + return new String(resultBytes, StandardCharsets.UTF_8); } - startIndex += fromBytes.length; - int endIndex = indexOf(bytes, toBytes, startIndex); - if (endIndex == -1) { - return null; - } + /** + * 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[] resultBytes = Arrays.copyOfRange(bytes, startIndex, endIndex); - return new String(resultBytes, StandardCharsets.UTF_8); - } + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); - /** 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; - } + int patternIndex = indexOf(strBytes, patternBytes, 0); + int patternOpenIndex = indexOf(patternBytes, OPEN.getBytes(StandardCharsets.UTF_8), 0); + if (patternIndex == -1) { + return null; + } - byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); - byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); + int openIndex = patternIndex + patternOpenIndex; + int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); - int patternIndex = indexOf(strBytes, patternBytes, 0); - int patternOpenIndex = indexOf(patternBytes, OPEN.getBytes(StandardCharsets.UTF_8), 0); - if (patternIndex == -1) { - return null; + if (closeIndex == -1) { + return null; + } + + 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 openIndex = patternIndex + patternOpenIndex; - int closeIndex = findPosOfClosingBracket(strBytes, patternIndex); + /** + * 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 (closeIndex == -1) { - return null; - } + if (isNull(str) || !str.contains(pattern)) return result; - return new String(strBytes, openIndex, closeIndex - openIndex + 1, StandardCharsets.UTF_8) - .trim(); - } + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + byte[] patternBytes = pattern.getBytes(StandardCharsets.UTF_8); - /** Finds matching closing bracket in string starting with pattern */ - private static int findPosOfClosingBracket(byte[] strBytes, int patternIndex) { - Deque stack = new ArrayDeque<>(); + int fromIndex = 0; + final int dataToAddLen = 150; + int patternIndex = indexOf(strBytes, patternBytes, fromIndex); - 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; + 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 -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; } - 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]); - } + 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; } - return -1; - } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/ResultLastBlock.java b/liteclient/src/main/java/org/ton/java/liteclient/api/ResultLastBlock.java index 9d7a5bb9..66480ef6 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/api/ResultLastBlock.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/ResultLastBlock.java @@ -1,17 +1,14 @@ package org.ton.java.liteclient.api; import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import lombok.Data; import java.io.Serializable; import java.math.BigInteger; @Builder -@Getter -@ToString +@Data public class ResultLastBlock implements Serializable { - //private String fullBlockSeqno; //(-1,8000000000000000,1432551):8128C13B9E81D86A261AD4ECA74F7C831822697A6EFE442C5491539A412AF295:8DD4D47161D9A7296BB3906BE8F1D6F0B827EE8C9CD5EB10F697853A23893376 private String rootHash; // 8128C13B9E81D86A261AD4ECA74F7C831822697A6EFE442C5491539A412AF295 private String fileHash; // 8DD4D47161D9A7296BB3906BE8F1D6F0B827EE8C9CD5EB10F697853A23893376 private Long wc; // -1 diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/AccountBlock.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/AccountBlock.java index d5a4ac11..41dbe885 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/api/block/AccountBlock.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/AccountBlock.java @@ -5,14 +5,32 @@ import lombok.ToString; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; @Builder @ToString @Getter public class AccountBlock implements Serializable { - //String accountAddr private List transactions; - //String oldHash - //String newHash + + public List getTransactionsWithBlockInfo(Info blockInfo) { + List txs = transactions; + for (Transaction tx : txs) { + tx.setBlockInfo(blockInfo); + } + return txs; + } + + public List getTransactionsWithBlockInfo(Info blockInfo, String address) { + List result = new ArrayList<>(); + for (Transaction tx : transactions) { + // if (tx.accountAddr.equals(address)) { + if (address.contains(tx.accountAddr.toLowerCase())) { + tx.setBlockInfo(blockInfo); + result.add(tx); + } + } + return result; + } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/Block.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/Block.java index 7c5a424e..2e3b1445 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/api/block/Block.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/Block.java @@ -1,15 +1,16 @@ package org.ton.java.liteclient.api.block; import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; @Builder -@Getter -@ToString +@Data +@Slf4j public class Block implements Serializable { Long globalId; Info info; @@ -18,10 +19,50 @@ public class Block implements Serializable { Extra extra; public List listBlockTrans() { - return extra.getAccountBlock().getTransactions(); + return extra.getAccountBlock().getTransactionsWithBlockInfo(info); + } + + public List listBlockTrans(String address) { + return extra.getAccountBlock().getTransactionsWithBlockInfo(info, address); } public List allShards() { return extra.getMasterchainBlock().getShardHashes(); } + + public void printAllTransactions() { + List txs = listBlockTrans(); + if (txs.isEmpty()) { + log.info("No transactions"); + return; + } + Transaction.printTxHeader(""); + for (Transaction tx : txs) { + tx.printTransactionFees(); + } + Transaction.printTxFooter(); + } + + public List getAllMessageFees() { + List txs = listBlockTrans(); + List msgFees = new ArrayList<>(); + for (Transaction tx : txs) { + msgFees.addAll(tx.getAllMessageFees()); + } + + return msgFees; + } + + public void printAllMessages() { + List msgFees = getAllMessageFees(); + if (msgFees.isEmpty()) { + log.info("No messages"); + return; + } + MessageFees.printMessageFeesHeader(); + for (MessageFees msgFee : msgFees) { + msgFee.printMessageFees(); + } + MessageFees.printMessageFeesFooter(); + } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/Info.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/Info.java index e7cda8f5..abe960b9 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/api/block/Info.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/Info.java @@ -1,23 +1,22 @@ package org.ton.java.liteclient.api.block; -import lombok.Builder;import lombok.Data; +import lombok.Builder; import lombok.Getter; import lombok.ToString; +import org.ton.java.utils.Utils; import java.io.Serializable; import java.math.BigInteger; /** - * Block header. Another non-split component of a shardchain block - * is the block header, which contains general information such as (w, s) (i.e., - * the workchain_id and the common binary prefix of all account_ids assigned - * to the current shardchain), the block’s sequence number (defined to be the - * smallest non-negative integer larger than the sequence numbers of its predecessors), - * logical time, and generation unixtime. It also contains the hash of - * the immediate antecessor of the block (or of its two immediate antecessors - * in the case of a preceding shardchain merge event), the hashes of its initial - * and final states (i.e., of the states of the shardchain immediately before and - * immediately after processing the current block), and the hash of the most + * Block header. Another non-split component of a shardchain block is the block header, which + * contains general information such as (w, s) (i.e., the workchain_id and the common binary prefix + * of all account_ids assigned to the current shardchain), the block’s sequence number (defined to + * be the smallest non-negative integer larger than the sequence numbers of its predecessors), + * logical time, and generation unixtime. It also contains the hash of the immediate antecessor of + * the block (or of its two immediate antecessors in the case of a preceding shardchain merge + * event), the hashes of its initial and final states (i.e., of the states of the shardchain + * immediately before and immediately after processing the current block), and the hash of the most * recent masterchain block known when the shardchain block was generated. */ @Builder @@ -48,4 +47,12 @@ public class Info implements Serializable { BigInteger prevEndLt; String prevRootHash; String prevFileHash; + BigInteger shardPrefix; + Integer shardPrefixBits; + Long shardWorkchain; + + public String getShortBlockSeqno() { + return String.format( + "(%d,%s,%d)", wc, Utils.convertShardIdentToShard(shardPrefix, shardPrefixBits), seqNo); + } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/LiteClientAddress.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/LiteClientAddress.java index dc5f2023..8830f15c 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/api/block/LiteClientAddress.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/LiteClientAddress.java @@ -6,10 +6,20 @@ import java.io.Serializable; +import static java.util.Objects.nonNull; + @Builder @ToString @Getter public class LiteClientAddress implements Serializable { - Long wc; - String addr; + Long wc; + String addr; + + public String getAddress() { + if (nonNull(wc)) { + return wc + ":" + addr; + } else { + return ""; + } + } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/MessageFees.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/MessageFees.java new file mode 100644 index 00000000..aacf98a0 --- /dev/null +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/MessageFees.java @@ -0,0 +1,88 @@ +package org.ton.java.liteclient.api.block; + +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ton.java.utils.Utils; + +import java.math.BigInteger; + +import static java.util.Objects.isNull; + +@Builder +@Data +@Slf4j +public class MessageFees { + String direction; + String type; + String op; + BigInteger fwdFee; + BigInteger value; + BigInteger ihrFee; + BigInteger createdAt; + BigInteger createdLt; + BigInteger importFee; + String src; + String dst; + + public void printMessageFees() { + MessageFees msgFee = this; + + String str = + String.format( + "| %-7s| %-13s| %-9s| %-16s| %-16s| %-16s| %-16s| %-20s| %-15s| %-68s| %-67s |", + msgFee.getDirection(), + msgFee.getType(), + msgFee.getOp(), + isNull(msgFee.getValue()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getValue()), + isNull(msgFee.getFwdFee()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getFwdFee()), + isNull(msgFee.getIhrFee()) ? "N/A" : Utils.formatNanoValueZero(msgFee.getIhrFee()), + isNull(msgFee.getImportFee()) + ? "N/A" + : Utils.formatNanoValueZero(msgFee.getImportFee()), + isNull(msgFee.getCreatedAt()) + ? "N/A" + : (msgFee.getCreatedAt().longValue() == 0) + ? "0" + : Utils.toUTC(msgFee.getCreatedAt().longValue()), + isNull(msgFee.getCreatedLt()) ? "N/A" : msgFee.getCreatedLt().toString(), + getSrc(), + getDst()); + log.info(str); + } + + public static void printMessageFeesHeader() { + String header = + "| in/out | type | op | value | fwdFee | ihrFee | importFee | timestamp | lt | source | destination |"; + log.info(""); + log.info("Messages"); + log.info( + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + log.info(header); + log.info( + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + } + + public static void printMessageFeesFooter() { + log.info( + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + } + + public String getSrc() { + if (StringUtils.isNotEmpty(src)) { + return src.substring(0, 7) + "..." + src.substring(src.length() - 6, src.length() - 1); + } else { + return "N/A"; + } + } + + public String getDst() { + if (StringUtils.isNotEmpty(dst)) { + return dst.substring(0, 7) + "..." + dst.substring(dst.length() - 6, dst.length() - 1); + + } else { + return "N/A"; + } + } +} diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/Transaction.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/Transaction.java index 101502c4..3ff58e4c 100644 --- a/liteclient/src/main/java/org/ton/java/liteclient/api/block/Transaction.java +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/Transaction.java @@ -1,16 +1,22 @@ package org.ton.java.liteclient.api.block; import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.ton.java.utils.Utils; import java.io.Serializable; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + @Builder -@ToString -@Getter +@Data +@Slf4j public class Transaction implements Serializable { String origStatus; @@ -35,4 +41,363 @@ public class Transaction implements Serializable { String oldHash; String newHash; TransactionDescription description; + + Info blockInfo; + + private String getAccountAddrShort() { + try { + if (StringUtils.isNotEmpty(accountAddr)) { + + String str64 = StringUtils.leftPad(accountAddr, 64, "0"); + return str64.substring(0, 5) + + "..." + + str64.substring(str64.length() - 6, str64.length() - 1); + } else { + return "N/A"; + } + } catch (Exception e) { + return "error"; + } + } + + private BigInteger getTotalForwardFees(Transaction tx) { + try { + return tx.getDescription().getAction().getTotalFwdFee().toBigInteger(); + } catch (Exception e) { + return BigInteger.ZERO; + } + } + + private BigInteger getComputeFees(Transaction tx) { + try { + return tx.getDescription().getCompute().getGasFees().toBigInteger(); + } catch (Exception e) { + return BigInteger.ZERO; + } + } + + private long getExitCode(Transaction tx) { + try { + return tx.getDescription().getCompute().getExitCode().longValue(); + } catch (Exception e) { + return 0; + } + } + + private long getActionCode(Transaction tx) { + try { + return tx.getDescription().getAction().getResultCode().longValue(); + } catch (Exception e) { + return 0; + } + } + + private long getTotalActions(Transaction tx) { + try { + return tx.getDescription().getAction().getTotActions().longValue(); + } catch (Exception e) { + return 0; + } + } + + public TransactionFees getTransactionFees() { + Transaction tx = this; + + BigInteger totalFees = tx.totalFees.getToncoins().toBigInteger(); + BigInteger totalForwardFees = getTotalForwardFees(tx); + BigInteger computeFees = getComputeFees(tx); + + BigInteger inForwardFees = BigInteger.ZERO; + BigInteger valueIn = BigInteger.ZERO; + BigInteger valueOut = BigInteger.ZERO; + BigInteger op = null; + long exitCode = getExitCode(tx); + long actionCode = getActionCode(tx); + long totalActions = getTotalActions(tx); + long now = tx.getNow(); + BigInteger lt = tx.getLt(); + long outMsgs = tx.getOutMsgsCount(); + + Message inMsg = tx.getInMsg(); + if (nonNull(inMsg)) { + + op = + nonNull(inMsg.getBody()) + ? new BigInteger(inMsg.getBody().getCells().get(0), 16) + : BigInteger.ONE.negate(); + + valueIn = inMsg.getValue().getToncoins().toBigInteger(); + inForwardFees = inMsg.getFwdFee().toBigInteger(); + + } + + for (Message outMsg : tx.getOutMsgs()) { + if (outMsg.getType().equals("Internal")) { + valueOut = valueOut.add(outMsg.getValue().getToncoins().toBigInteger()); + } + } + + return TransactionFees.builder() + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) ? op.toString(16) : "no body") + .type(tx.getDescription().getType()) + .valueIn(valueIn) + .valueOut(valueOut) + .totalFees(totalFees) + .outForwardFee(totalForwardFees) + .computeFee(computeFees) + .inForwardFee(inForwardFees) + .exitCode(exitCode) + .actionCode(actionCode) + .totalActions(totalActions) + .aborted(nonNull(tx.getDescription().aborted) && tx.getDescription().aborted == 1) + .now(now) + .lt(lt) + .account(nonNull(tx.getAccountAddr()) ? tx.getAccountAddr() : "") + .block(tx.getBlockInfo().getShortBlockSeqno()) + .hash("can't get") + .build(); + } + + public void printTransactionFees() { + printTransactionFees(false, false); + } + + public void printTransactionFees(boolean withHeader, boolean withFooter, String balance) { + TransactionFees txFees = getTransactionFees(); + + if (withHeader) { + printTxHeader(" (initial balance " + balance + ")"); + } + printTxData(txFees); + + if (withFooter) { + printTxFooter(); + } + } + + public void printTransactionFees(boolean withHeader, boolean withFooter) { + TransactionFees txFees = getTransactionFees(); + + if (withHeader) { + printTxHeader(""); + } + printTxData(txFees); + + if (withFooter) { + printTxFooter(); + } + } + + public List getAllMessageFees() { + List messageFees = new ArrayList<>(); + Transaction tx = this; + + BigInteger op; + + Message inMsg = tx.getInMsg(); + if (nonNull(inMsg)) { + + op = + nonNull(inMsg.getBody()) + ? new BigInteger(inMsg.getBody().getCells().get(0), 16) + : BigInteger.ONE.negate(); + + if (inMsg.getType().equals("Internal")) { + MessageFees msgFee = + MessageFees.builder() + .direction("in") + .type("internal-in") + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .value(inMsg.getValue().getToncoins().toBigInteger()) + .fwdFee(inMsg.getFwdFee().toBigInteger()) + .ihrFee(inMsg.getIhrFee().toBigInteger()) + .createdLt(inMsg.getCreatedLt()) + .createdAt(inMsg.getCreatedAt()) + .src(inMsg.getSrcAddr().getAddress()) + .dst(inMsg.getDestAddr().getAddress()) + .build(); + messageFees.add(msgFee); + } + if (inMsg.getType().equals("External Out")) { + MessageFees msgFee = + MessageFees.builder() + .direction("in") + .type("external-out") + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .createdLt(inMsg.getCreatedLt()) + .createdAt(inMsg.getCreatedAt()) + .src(inMsg.getSrcAddr().getAddress()) + .dst(inMsg.getDestAddr().getAddress()) + .build(); + messageFees.add(msgFee); + } + if (inMsg.getType().equals("External In")) { + MessageFees msgFee = + MessageFees.builder() + .direction("in") + .type("external-in") + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .importFee(inMsg.getImportFee().toBigInteger()) + .src(inMsg.getSrcAddr().getAddress()) + .dst(inMsg.getDestAddr().getAddress()) + .build(); + messageFees.add(msgFee); + } + } + + for (Message outMsg : tx.getOutMsgs()) { + + op = + nonNull(outMsg.getBody()) + ? new BigInteger(outMsg.getBody().getCells().get(0), 16) + : BigInteger.ONE.negate(); + + if (outMsg.getType().equals("InternalMessageInfo")) { + + MessageFees msgFee = + MessageFees.builder() + .direction("out") + .type(formatMsgType("InternalMessageInfo")) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .value(outMsg.getValue().getToncoins().toBigInteger()) + .fwdFee(outMsg.getFwdFee().toBigInteger()) + .ihrFee(outMsg.getIhrFee().toBigInteger()) + .createdLt(outMsg.getCreatedLt()) + .createdAt(outMsg.getCreatedAt()) + .src(outMsg.getSrcAddr().getAddress()) + .dst(outMsg.getDestAddr().getAddress()) + .build(); + messageFees.add(msgFee); + } + if (outMsg.getType().equals("ExternalMessageOutInfo")) { + + MessageFees msgFee = + MessageFees.builder() + .direction("out") + .type(formatMsgType("ExternalMessageOutInfo")) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .createdLt(outMsg.getCreatedLt()) + .createdAt(outMsg.getCreatedAt()) + .src(outMsg.getSrcAddr().getAddress()) + .dst(outMsg.getDestAddr().getAddress()) + .build(); + messageFees.add(msgFee); + } + if (outMsg.getType().equals("ExternalMessageInInfo")) { + + MessageFees msgFee = + MessageFees.builder() + .direction("out") + .type(formatMsgType("ExternalMessageInInfo")) + .op( + (isNull(op)) + ? "N/A" + : (op.compareTo(BigInteger.ONE.negate()) != 0) + ? op.toString(16) + : "no body") + .importFee(outMsg.getImportFee().toBigInteger()) + .src(outMsg.getSrcAddr().getAddress()) + .dst(outMsg.getDestAddr().getAddress()) + .build(); + messageFees.add(msgFee); + } + } + + return messageFees; + } + + public void printAllMessages(boolean withHeader) { + List msgFees = getAllMessageFees(); + if (msgFees.isEmpty()) { + // log.info("No messages"); + return; + } + + if (withHeader) { + MessageFees.printMessageFeesHeader(); + } + + for (MessageFees msgFee : msgFees) { + msgFee.printMessageFees(); + } + // MessageFees.printMessageFeesFooter(); + } + + public static void printTxHeader(String balance) { + log.info(""); + log.info("Transactions" + balance); + log.info( + "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + log.info( + "| txHash | time | op | type | valueIn | valueOut | totalFees | inForwardFee | outForwardFee | outActions | outMsgs | computeFee | aborted | exitCode | actionCode | account | block |"); + log.info( + "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + } + + public static void printTxFooter() { + log.info( + "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + } + + public static void printTxData(TransactionFees txFees) { + String str = + String.format( + "| %-8s | %-8s | %-9s| %-13s| %-15s| %-15s| %-20s| %-13s| %-14s| %-11s| %-8s| %-20s| %-8s| %-9s| %-11s| %-13s | %-31s |", + txFees.getHash().substring(0, 5), + Utils.toUTCTimeOnly(txFees.getNow()), + txFees.getOp(), + txFees.getType(), + Utils.formatNanoValueZero(txFees.getValueIn()), + Utils.formatNanoValueZero(txFees.getValueOut()), + Utils.formatNanoValueZero(txFees.getTotalFees()), + Utils.formatNanoValueZero(txFees.getInForwardFee()), + Utils.formatNanoValueZero(txFees.getOutForwardFee()), + txFees.getTotalActions(), + txFees.getOutMsgs(), + Utils.formatNanoValueZero(txFees.getComputeFee()), + txFees.isAborted(), + txFees.getExitCode(), + txFees.getActionCode(), + txFees.getAccount(), + txFees.getBlock()); + log.info(str); + } + + private String formatMsgType(String fullMsgType) { + if (fullMsgType.equals("InternalMessageInfo")) { + return "internal-in"; + } else if (fullMsgType.equals("ExternalMessageOutInfo")) { + return "external-out"; + } else { + return "external-in"; + } + } } diff --git a/liteclient/src/main/java/org/ton/java/liteclient/api/block/TransactionFees.java b/liteclient/src/main/java/org/ton/java/liteclient/api/block/TransactionFees.java new file mode 100644 index 00000000..a80b1059 --- /dev/null +++ b/liteclient/src/main/java/org/ton/java/liteclient/api/block/TransactionFees.java @@ -0,0 +1,30 @@ +package org.ton.java.liteclient.api.block; + +import lombok.Builder; +import lombok.Data; + +import java.math.BigInteger; + +@Builder +@Data +public class TransactionFees { + String op; + String type; + BigInteger totalFees; + BigInteger computeFee; + BigInteger inForwardFee; + BigInteger outForwardFee; + BigInteger valueIn; + BigInteger valueOut; + long exitCode; + long actionCode; + long totalActions; + long outMsgs; + long now; + BigInteger lt; + String account; + BigInteger balance; + boolean aborted; + String block; + String hash; +} \ No newline at end of file diff --git a/liteclient/src/main/resources/testnet-global.config.json b/liteclient/src/main/resources/testnet-global.config.json index 12ae663c..d476fbbd 100644 --- a/liteclient/src/main/resources/testnet-global.config.json +++ b/liteclient/src/main/resources/testnet-global.config.json @@ -1,140 +1,12 @@ { "liteservers": [ { - "ip": 1592601963, - "port": 13833, - "id": { - "@type": "pub.ed25519", - "key": "QpVqQiv1u3nCHuBR3cg3fT6NqaFLlnLGbEgtBRukDpU=" - } - }, - { - "ip": 1097649206, - "port": 29296, - "id": { - "@type": "pub.ed25519", - "key": "p2tSiaeSqX978BxE5zLxuTQM06WVDErf5/15QToxMYA=" - } - }, - { - "ip": 1162057690, - "port": 35939, - "id": { - "@type": "pub.ed25519", - "key": "97y55AkdzXWyyVuOAn+WX6p66XTNs2hEGG0jFUOkCIo=" - } - }, - { - "ip": -1304477830, - "port": 20700, - "id": { - "@type": "pub.ed25519", - "key": "dGLlRRai3K9FGkI0dhABmFHMv+92QEVrvmTrFf5fbqA=" - } - }, - { - "ip": 1959453117, - "port": 20700, - "id": { - "@type": "pub.ed25519", - "key": "24RL7iVI20qcG+j//URfd/XFeEG9qtezW2wqaYQgVKw=" - } - }, - { - "ip": -809760973, - "port": 20700, - "id": { - "@type": "pub.ed25519", - "key": "vunMV7K35yPlTQPx/Fqk6s+4/h5lpcbP+ao0Cy3M2hw=" - } - }, - { - "ip": -1177439932, - "port": 4695, - "id": { - "@type": "pub.ed25519", - "key": "cZpMFqy6n0Lsu8x/z2Jq0wh/OdM1WAVJJKSb2CvDECQ=" - } - }, - { - "ip": -809760945, - "port": 41718, - "id": { - "@type": "pub.ed25519", - "key": "jA1X1pNB+ihJ4tziHTD8KxKWdQESRXjDb79TgvFFOZg=" - } - }, - { - "ip": 1162057633, + "ip": 1495755553, "port": 59672, "id": { "@type": "pub.ed25519", "key": "WqVn3UcFKCLaGCVp1FOZ09duh13tRqUR+rTaA9Q9sW0=" } - }, - { - "ip": -2018162320, - "port": 49760, - "id": { - "@type": "pub.ed25519", - "key": "1runGS/h6Pel2LRC46suIEKaOtAYWaDGA+cXeI4HXGo=" - } - }, - { - "ip": -2018162357, - "port": 47938, - "id": { - "@type": "pub.ed25519", - "key": "tmnh97x53cR/oejeISkTxkTyWznvIwUQrd2nZFpkbWE=" - } - }, - { - "ip": 1091914382, - "port": 21335, - "id": { - "@type": "pub.ed25519", - "key": "O8PmvAwKM7n5JAQaW+q8NWKiip89eh1u9FuJZWrGvgs=" - } - }, - { - "ip": 1091914380, - "port": 46427, - "id": { - "@type": "pub.ed25519", - "key": "JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=" - } - }, - { - "ip": 1097633201, - "port": 17439, - "id": { - "@type": "pub.ed25519", - "key": "0MIADpLH4VQn+INHfm0FxGiuZZAA8JfTujRqQugkkA8=" - } - }, - { - "ip": 1091956407, - "port": 16351, - "id": { - "@type": "pub.ed25519", - "key": "Mf/JGvcWAvcrN3oheze8RF/ps6p7oL6ifrIzFmGQFQ8=" - } - }, - { - "ip": -1185526389, - "port": 64842, - "id": { - "@type": "pub.ed25519", - "key": "cmpsvK5tBuW029x0WnLHV4NAzf5F0wxEagtbODtRvjI=" - } - }, - { - "ip": -1185526601, - "port": 11087, - "id": { - "@type": "pub.ed25519", - "key": "NeOgnMj1Z3xvy9Tq2yAbnGf7HrSMeNNr+ba4WY3Gs+E=" - } } ], "dht": { 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 aa411302..59c5d575 100644 --- a/liteclient/src/test/java/org/ton/java/liteclient/LiteClientTest.java +++ b/liteclient/src/test/java/org/ton/java/liteclient/LiteClientTest.java @@ -1,329 +1,422 @@ 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; import org.junit.BeforeClass; import org.junit.Test; +import org.ton.java.bitstring.BitString; +import org.ton.java.cell.Cell; +import org.ton.java.cell.CellBuilder; import org.ton.java.liteclient.api.ResultLastBlock; import org.ton.java.liteclient.api.ResultListBlockTransactions; import org.ton.java.liteclient.api.block.Block; +import org.ton.java.liteclient.api.block.MessageFees; import org.ton.java.liteclient.api.block.Transaction; +import org.ton.java.utils.Utils; -/** Integration Tests that run against live testnet */ +import java.io.File; +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 + */ @Slf4j public class LiteClientTest { - 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()); - } + 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 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 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()); } - txs.forEach(System.out::println); + @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())); + + 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())); + } + + @Test + public void testPrintAllMsgInBlock() throws Exception { + + String stdoutLast = liteClient.executeLast(); + assertNotNull(stdoutLast); + ResultLastBlock blockIdLast = LiteClientParser.parseLast(stdoutLast); + String stdoutAllShards = liteClient.executeAllshards(blockIdLast); + // log.info(stdoutAllShards); + + assertNotNull(stdoutAllShards); + List shards = LiteClientParser.parseAllShards(stdoutAllShards); + for (ResultLastBlock shard : shards) { + log.info("shard {}", shard); + String stdoutDumpblock = liteClient.executeDumpblock(shard); + + Block block = LiteClientParser.parseDumpblock(stdoutDumpblock, false, false); + assertNotNull(block); + log.info("block {}", block); + + block.printAllTransactions(); + block.printAllMessages(); + } + } // shard:(shard_ident shard_pfx_bits:2 workchain_id:0 shard_prefix:0) + + @Test + public void testPrintAllMsgInMasterBlock() { + + List result = liteClient.getAllTransactionsFromLatestBlock(); + + Transaction.printTxHeader(""); + for (Transaction tx : result) { + tx.printTransactionFees(); + } + Transaction.printTxFooter(); + + MessageFees.printMessageFeesHeader(); + for (Transaction tx : result) { + tx.printAllMessages(false); + } + MessageFees.printMessageFeesFooter(); + } + + @Test + public void testPrintAllMsgInAllShards() { + + LiteClient liteClient = LiteClient.builder().testnet(true).build(); + + List result = liteClient.getAllTransactionsFromLatestBlockAndAllShards(); + + Transaction.printTxHeader(""); + for (Transaction tx : result) { + tx.printTransactionFees(); + } + Transaction.printTxFooter(); + + MessageFees.printMessageFeesHeader(); + for (Transaction tx : result) { + tx.printAllMessages(false); + } + MessageFees.printMessageFeesFooter(); + } - 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 testRunMethodTest1() throws Exception { + LiteClient liteClient = LiteClient.builder().testnet(false).build(); + final String result = + liteClient.executeRunMethod( + "EQD-BJSVUJviud_Qv7Ymfd3qzXdrmV525e3YDzWQoHIAiInL", + "get_source_item_address", + "48254587912360971182213375080930396471362566750482580908974048855188723745203 103493475297594561220733205176087411411758133058754107407060723644815771644946"); + log.info("result {}", result); + + byte[] b = + Utils.hexToSignedBytes( + "801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0"); + BitString bs = new BitString(); + bs.writeBytes(b); + Cell c = CellBuilder.beginCell().storeBitString(bs, 267).endCell(); + + log.info(" boc {}", c.toHex(true, false)); + log.info(" boc {}", c.toBase64(true)); + // te6cckEBAQEAJAAAQ4AUgr8E0Xac8LWfX/1Mv2WbXh2d3S7MxHkB7CkkKz/Xb7BxEejY + // te6cckEBAQEAJAAAQ4AUgr8E0Xac8LWfX/1Mv2WbXh2d3S7MxHkB7CkkKz/Xb7BxEejY } - } - - @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/liteclient/src/test/resources/logback.xml b/liteclient/src/test/resources/logback.xml new file mode 100644 index 00000000..2c36c50b --- /dev/null +++ b/liteclient/src/test/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %msg%n + + + + + + + + \ No newline at end of file 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 b0476bf0..81ad5080 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java @@ -1,10 +1,5 @@ package org.ton.java.smartcontract; -import static java.util.Objects.isNull; - -import java.io.File; -import java.net.URL; -import java.nio.file.Paths; import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -15,6 +10,13 @@ import org.ton.java.func.FuncRunner; import org.ton.java.tolk.TolkRunner; +import java.io.File; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Paths; + +import static java.util.Objects.isNull; + /** * Make sure you have fift and func installed. See packages for instructions. @@ -24,120 +26,118 @@ @Slf4j public class SmartContractCompiler { - String contractPath; - String contractAsResource; - - private FiftRunner fiftRunner; + String contractPath; + String contractAsResource; - private FuncRunner funcRunner; + private FiftRunner fiftRunner; - private TolkRunner tolkRunner; + private FuncRunner funcRunner; - private Boolean printInfo; + private TolkRunner tolkRunner; - private boolean printFiftAsmOutput; + private Boolean printInfo; - public static class SmartContractCompilerBuilder {} + private boolean printFiftAsmOutput; - public static SmartContractCompilerBuilder builder() { - return new CustomSmartContractCompilerBuilder(); - } - - private static class CustomSmartContractCompilerBuilder extends SmartContractCompilerBuilder { - @Override - public SmartContractCompiler build() { - if (isNull(super.printInfo)) { - super.printInfo = true; - } - if (isNull(super.funcRunner)) { - super.funcRunner = FuncRunner.builder().build(); - } - if (isNull(super.fiftRunner)) { - super.fiftRunner = FiftRunner.builder().build(); - } - if (isNull(super.tolkRunner)) { - super.tolkRunner = TolkRunner.builder().build(); - } - return super.build(); - } - } - - /** - * Compile to Boc in hex format - * - * @return code of BoC in hex - */ - public String compile() { - if (StringUtils.isNotEmpty(contractAsResource)) { - try { - URL resource = SmartContractCompiler.class.getClassLoader().getResource(contractAsResource); - contractPath = Paths.get(resource.toURI()).toFile().getAbsolutePath(); - } catch (Exception e) { - throw new Error("Can't find resource " + contractAsResource); - } - } - if (isNull(printInfo)) { - log.info("workdir " + new File(contractPath).getParent()); + public static class SmartContractCompilerBuilder { } - String outputFiftAsmFile; - if (contractPath.contains(".func") || contractPath.contains(".fc")) { - outputFiftAsmFile = funcRunner.run(new File(contractPath).getParent(), contractPath); - // add missing includes, PROGRAM and to boc conversion - - outputFiftAsmFile = - "\"TonUtil.fif\" include \"Asm.fif\" include PROGRAM{ " - + outputFiftAsmFile - + "}END>c 2 boc+>B dup Bx."; - outputFiftAsmFile = - outputFiftAsmFile - .replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "") - .replaceAll("\n", " ") - .replaceAll("\r", " "); - - } else { // tolk - outputFiftAsmFile = tolkRunner.run(new File(contractPath).getParent(), contractPath); - - outputFiftAsmFile = - "\"TonUtil.fif\" include " - +outputFiftAsmFile - +" 2 boc+>B dup Bx."; - - outputFiftAsmFile = - outputFiftAsmFile - .replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "") - .replaceAll("\n", " ") - .replaceAll("\r", " "); + public static SmartContractCompilerBuilder builder() { + return new CustomSmartContractCompilerBuilder(); } - if (outputFiftAsmFile.contains("cannot generate code") - || outputFiftAsmFile.contains("error: undefined function") || outputFiftAsmFile.contains("Failed to discover")) { - throw new Error("Compile error: " + outputFiftAsmFile); + private static class CustomSmartContractCompilerBuilder extends SmartContractCompilerBuilder { + @Override + public SmartContractCompiler build() { + if (isNull(super.printInfo)) { + super.printInfo = true; + } + if (isNull(super.funcRunner)) { + super.funcRunner = FuncRunner.builder().build(); + } + if (isNull(super.fiftRunner)) { + super.fiftRunner = FiftRunner.builder().build(); + } + if (isNull(super.tolkRunner)) { + super.tolkRunner = TolkRunner.builder().build(); + } + return super.build(); + } } - if (printFiftAsmOutput) { - log.info(outputFiftAsmFile); + /** + * Compile to Boc in hex format + * + * @return code of BoC in hex + */ + public String compile() { + if (StringUtils.isNotEmpty(contractAsResource)) { + try { + URL resource = SmartContractCompiler.class.getClassLoader().getResource(contractAsResource); + contractPath = Paths.get(resource.toURI()).toFile().getAbsolutePath(); + } catch (Exception e) { + throw new Error("Can't find resource " + contractAsResource); + } + } + if (isNull(printInfo)) { + log.info("workdir " + new File(contractPath).getParent()); + } + + String outputFiftAsmFile; + if (contractPath.contains(".func") || contractPath.contains(".fc")) { + outputFiftAsmFile = funcRunner.run(new File(contractPath).getParent(), contractPath); + // add missing includes, PROGRAM and to boc conversion + + outputFiftAsmFile = + "\"TonUtil.fif\" include \"Asm.fif\" include PROGRAM{ " + + outputFiftAsmFile + + "}END>c 2 boc+>B dup Bx."; + outputFiftAsmFile = + outputFiftAsmFile + .replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "") + .replaceAll("\n", " ") + .replaceAll("\r", " "); + + } else { // tolk + outputFiftAsmFile = tolkRunner.run(new File(contractPath).getParent(), contractPath); + + outputFiftAsmFile = "\"TonUtil.fif\" include " + outputFiftAsmFile + " 2 boc+>B dup Bx."; + + outputFiftAsmFile = + outputFiftAsmFile + .replaceAll("(?:/\\*(?:[^*]|(?:\\*+[^*/]))*\\*+/)|(?://.*)", "") + .replaceAll("\n", " ") + .replaceAll("\r", " "); + } + + if (outputFiftAsmFile.contains("cannot generate code") + || outputFiftAsmFile.contains("error: undefined function") + || outputFiftAsmFile.contains("Failed to discover")) { + throw new Error("Compile error: " + outputFiftAsmFile); + } + + if (printFiftAsmOutput) { + log.info(outputFiftAsmFile); + } + + String result; + try { + File fiftFile = new File(contractPath + ".fif"); + FileUtils.writeStringToFile(fiftFile, outputFiftAsmFile, Charset.defaultCharset()); + result = fiftRunner.run(new File(contractPath).getParent(), "-s", fiftFile.getAbsolutePath()); + FileUtils.deleteQuietly(fiftFile); + return result; + } catch (Exception e) { + throw new Error("Cannot compile " + contractPath + ", error " + e.getMessage()); + } } - String result; - try { - File fiftFile = new File(contractPath + ".fif"); - FileUtils.writeStringToFile(fiftFile, outputFiftAsmFile); - result = fiftRunner.run(new File(contractPath).getParent(), "-s", fiftFile.getAbsolutePath()); - FileUtils.deleteQuietly(fiftFile); - return result; - } - catch (Exception e) { - throw new Error("Cannot compile "+contractPath+ ", error " + e.getMessage()); - } - } - - /** - * Compile to Cell - * - * @return Cell - */ - public Cell compileToCell() { - return Cell.fromBoc(compile()); - } + /** + * Compile to Cell + * + * @return Cell + */ + public Cell compileToCell() { + return Cell.fromBoc(compile()); + } } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java index 9da84cf3..4df9a551 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java @@ -1,7 +1,5 @@ package org.ton.java.smartcontract.wallet; -import java.math.BigInteger; -import java.util.List; import org.apache.commons.lang3.StringUtils; import org.ton.java.address.Address; import org.ton.java.cell.Cell; @@ -14,168 +12,173 @@ import org.ton.java.tonlib.types.RawTransaction; import org.ton.java.utils.Utils; -/** Interface for all smart contract objects in ton4j. */ +import java.math.BigInteger; +import java.util.List; + +/** + * Interface for all smart contract objects in ton4j. + */ public interface Contract { - Tonlib getTonlib(); - - /** - * Used for late tonlib assignment - * - * @param pTonlib Tonlib instance - */ - void setTonlib(Tonlib pTonlib); - - long getWorkchain(); - - String getName(); - - default Address getAddress() { - return StateInit.builder() - .code(createCodeCell()) - .data(createDataCell()) - .build() - .getAddress(getWorkchain()); - } - - default Address getAddress(byte workchain) { - return getStateInit().getAddress(workchain); - } - - default MsgAddressIntStd getAddressIntStd() { - Address ownAddress = getStateInit().getAddress(getWorkchain()); - return MsgAddressIntStd.builder() - .workchainId(ownAddress.wc) - .address(ownAddress.toBigInteger()) - .build(); - } - - default MsgAddressIntStd getAddressIntStd(int workchain) { - Address ownAddress = getStateInit().getAddress(); - return MsgAddressIntStd.builder() - .workchainId((byte) workchain) - .address(ownAddress.toBigInteger()) - .build(); - } - - /** - * @return Cell containing contact code - */ - Cell createCodeCell(); - - /** - * Method to override - * - * @return {Cell} cell contains contract data - */ - Cell createDataCell(); - - default Cell createLibraryCell() { - return null; - } - - /** - * Message StateInit consists of initial contract code, data and address in a blockchain - * - * @return StateInit - */ - default StateInit getStateInit() { - return StateInit.builder() - .code(createCodeCell()) - .data(createDataCell()) - .lib(createLibraryCell()) - .build(); - } - - default long getSeqno() { - - if (this instanceof WalletV1R1) { - throw new Error("Wallet V1R1 does not have seqno method"); - } - - return getTonlib().getSeqno(getAddress()); - } - - default boolean isDeployed() { - return StringUtils.isNotEmpty(getTonlib().getRawAccountState(getAddress()).getCode()); - } - - default void waitForDeployment(int timeoutSeconds) { - System.out.println("waiting for deployment (up to " + timeoutSeconds + "s)"); - int i = 0; - do { - if (++i * 2 >= timeoutSeconds) { - throw new Error("Can't deploy contract within specified timeout."); - } - Utils.sleep(2); - } while (!isDeployed()); - } - - default void waitForBalanceChange(int timeoutSeconds) { - System.out.println( - "waiting for balance change (up to " - + timeoutSeconds - + "s) - " - + (getTonlib().isTestnet() - ? getAddress().toBounceableTestnet() - : getAddress().toBounceable()) - + " - (" - + getAddress().toRaw() - + ")"); - BigInteger initialBalance = getBalance(); - int i = 0; - do { - if (++i * 2 >= timeoutSeconds) { - throw new Error("Balance was not changed within specified timeout."); - } - Utils.sleep(2); - } while (initialBalance.equals(getBalance())); - } - - default BigInteger getBalance() { - return new BigInteger(getTonlib().getAccountState(getAddress()).getBalance()); - } - - default List getTransactions(int historyLimit) { - return getTonlib() - .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, historyLimit) - .getTransactions(); - } - - default List getTransactions() { - return getTonlib() - .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, 20) - .getTransactions(); - } - - default Message prepareDeployMsg() { - throw new Error("not implemented"); - } - - default Message prepareExternalMsg(WalletConfig config) { - throw new Error("not implemented"); - } - - default BigInteger getGasFees() { - switch (getName()) { - case "V1R1": - return BigInteger.valueOf(40000); // 0.00004 toncoins - case "V1R2": - return BigInteger.valueOf(40000); - case "V1R3": - return BigInteger.valueOf(40000); - case "V2R1": - return BigInteger.valueOf(40000); - case "V2R2": - return BigInteger.valueOf(40000); - case "V3R1": - return BigInteger.valueOf(40000); - case "V3R2": - return BigInteger.valueOf(40000); - case "V4R2": - return BigInteger.valueOf(310000); - default: - throw new Error("Unknown wallet version"); - } - } + Tonlib getTonlib(); + + /** + * Used for late tonlib assignment + * + * @param pTonlib Tonlib instance + */ + void setTonlib(Tonlib pTonlib); + + long getWorkchain(); + + String getName(); + + default Address getAddress() { + return StateInit.builder() + .code(createCodeCell()) + .data(createDataCell()) + .build() + .getAddress(getWorkchain()); + } + + default Address getAddress(byte workchain) { + return getStateInit().getAddress(workchain); + } + + default MsgAddressIntStd getAddressIntStd() { + Address ownAddress = getStateInit().getAddress(getWorkchain()); + return MsgAddressIntStd.builder() + .workchainId(ownAddress.wc) + .address(ownAddress.toBigInteger()) + .build(); + } + + default MsgAddressIntStd getAddressIntStd(int workchain) { + Address ownAddress = getStateInit().getAddress(); + return MsgAddressIntStd.builder() + .workchainId((byte) workchain) + .address(ownAddress.toBigInteger()) + .build(); + } + + /** + * @return Cell containing contact code + */ + Cell createCodeCell(); + + /** + * Method to override + * + * @return {Cell} cell contains contract data + */ + Cell createDataCell(); + + default Cell createLibraryCell() { + return null; + } + + /** + * Message StateInit consists of initial contract code, data and address in a blockchain + * + * @return StateInit + */ + default StateInit getStateInit() { + return StateInit.builder() + .code(createCodeCell()) + .data(createDataCell()) + .lib(createLibraryCell()) + .build(); + } + + default long getSeqno() { + + if (this instanceof WalletV1R1) { + throw new Error("Wallet V1R1 does not have seqno method"); + } + + return getTonlib().getSeqno(getAddress()); + } + + default boolean isDeployed() { + return StringUtils.isNotEmpty(getTonlib().getRawAccountState(getAddress()).getCode()); + } + + default void waitForDeployment(int timeoutSeconds) { + System.out.println("waiting for deployment (up to " + timeoutSeconds + "s)"); + int i = 0; + do { + if (++i * 2 >= timeoutSeconds) { + throw new Error("Can't deploy contract within specified timeout."); + } + Utils.sleep(2); + } while (!isDeployed()); + } + + default void waitForBalanceChange(int timeoutSeconds) { + System.out.println( + "waiting for balance change (up to " + + timeoutSeconds + + "s) - " + + (getTonlib().isTestnet() + ? getAddress().toBounceableTestnet() + : getAddress().toBounceable()) + + " (" + + getAddress().toRaw() + + ")"); + BigInteger initialBalance = getBalance(); + int i = 0; + do { + if (++i * 2 >= timeoutSeconds) { + throw new Error("Balance was not changed within specified timeout."); + } + Utils.sleep(2); + } while (initialBalance.equals(getBalance())); + } + + default BigInteger getBalance() { + return new BigInteger(getTonlib().getAccountState(getAddress()).getBalance()); + } + + default List getTransactions(int historyLimit) { + return getTonlib() + .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, historyLimit) + .getTransactions(); + } + + default List getTransactions() { + return getTonlib() + .getAllRawTransactions(getAddress().toBounceable(), BigInteger.ZERO, null, 20) + .getTransactions(); + } + + default Message prepareDeployMsg() { + throw new Error("not implemented"); + } + + default Message prepareExternalMsg(WalletConfig config) { + throw new Error("not implemented"); + } + + default BigInteger getGasFees() { + switch (getName()) { + case "V1R1": + return BigInteger.valueOf(40000); // 0.00004 toncoins + case "V1R2": + return BigInteger.valueOf(40000); + case "V1R3": + return BigInteger.valueOf(40000); + case "V2R1": + return BigInteger.valueOf(40000); + case "V2R2": + return BigInteger.valueOf(40000); + case "V3R1": + return BigInteger.valueOf(40000); + case "V3R2": + return BigInteger.valueOf(40000); + case "V4R2": + return BigInteger.valueOf(310000); + default: + throw new Error("Unknown wallet version"); + } + } } diff --git a/tl/src/main/java/org/ton/java/tl/types/Text.java b/tl/src/main/java/org/ton/java/tl/types/Text.java index cbd84c85..03a027f2 100644 --- a/tl/src/main/java/org/ton/java/tl/types/Text.java +++ b/tl/src/main/java/org/ton/java/tl/types/Text.java @@ -22,7 +22,7 @@ public class Text { @ToString.Exclude private byte[] val; @ToString.Exclude - private int chunksNum = 1; + private int chunksNum; public Cell toCell() { if (value.isEmpty()) { 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 6c712f55..7f1f561d 100644 --- a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java +++ b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java @@ -1,19 +1,9 @@ package org.ton.java.tonlib; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.ToNumberPolicy; import com.sun.jna.Native; - -import java.io.InputStream; -import java.math.BigInteger; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.*; -import java.util.concurrent.TimeUnit; import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -26,602 +16,631 @@ import org.ton.java.tonlib.types.globalconfig.*; import org.ton.java.utils.Utils; +import java.io.InputStream; +import java.math.BigInteger; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + @Slf4j @Builder public class Tonlib { - /** - * If not specified then tries to find tonlib in system folder, more info here - */ - public String pathToTonlibSharedLib; - - /** - * 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; - */ - public String pathToGlobalConfig; - - /** - * if not specified and pathToGlobalConfig is null then integrated global-config.json is used; - * - *

if not specified and pathToGlobalConfig is filled then pathToGlobalConfig is used; - */ - private String globalConfigAsString; - - private TonGlobalConfig globalConfig; - - /** - * Valid values are:
- * 0 - FATAL
- * 1 - ERROR
- * 2 - WARNING
- * 3 - INFO
- * 4 - DEBUG
- */ - private VerbosityLevel verbosityLevel; - - private Boolean ignoreCache; - - /** Ignored if pathToGlobalConfig is not null. */ - private boolean testnet; - - private boolean keystoreInMemory; - - private String keystorePath; - - private Integer liteServerIndex; - private Boolean usingAllLiteServers; - - /** Do not use! Reserved for internal usage. */ - private TonGlobalConfig originalGlobalConfigInternal; - - /** Do not use! Reserved for internal usage. */ - private String originalGlobalConfigStr; - - /** Default value 5 */ - private int receiveRetryTimes; + /** + * If not specified then tries to find tonlib in system folder, more info here + */ + public String pathToTonlibSharedLib; - /** In seconds. Default value 10.0 seconds */ - private double receiveTimeout; + /** + * 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; + */ + public String pathToGlobalConfig; - private TonlibJsonI tonlibJson; + /** + * if not specified and pathToGlobalConfig is null then integrated global-config.json is used; + * + *

if not specified and pathToGlobalConfig is filled then pathToGlobalConfig is used; + */ + private String globalConfigAsString; - private Boolean printInfo; + private TonGlobalConfig globalConfig; - private static final Gson gson = - new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL).create(); + /** + * Valid values are:
+ * 0 - FATAL
+ * 1 - ERROR
+ * 2 - WARNING
+ * 3 - INFO
+ * 4 - DEBUG
+ */ + private VerbosityLevel verbosityLevel; - private long tonlib; + private Boolean ignoreCache; - RunResultParser runResultParser; + /** + * Ignored if pathToGlobalConfig is not null. + */ + private boolean testnet; - LibraryResultParser libraryResultParser; + private boolean keystoreInMemory; - public static class TonlibBuilder {} + private String keystorePath; - public static TonlibBuilder builder() { - return new CustomTonlibBuilder(); - } + private Integer liteServerIndex; + private Boolean usingAllLiteServers; - private static class CustomTonlibBuilder extends TonlibBuilder { - @Override - public Tonlib build() { + /** + * Do not use! Reserved for internal usage. + */ + private TonGlobalConfig originalGlobalConfigInternal; - try { + /** + * Do not use! Reserved for internal usage. + */ + private String originalGlobalConfigStr; - if (isNull(super.printInfo)) { - super.printInfo = true; - } + /** + * Default value 5 + */ + private int receiveRetryTimes; - if (isNull(super.pathToTonlibSharedLib)) { - if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { - super.pathToTonlibSharedLib = Utils.detectAbsolutePath("tonlibjson", true); - } else { - super.pathToTonlibSharedLib = Utils.detectAbsolutePath("libtonlibjson", true); - } - } + /** + * In seconds. Default value 10.0 seconds + */ + private double receiveTimeout; - if (isNull(super.verbosityLevel)) { - super.verbosityLevel = VerbosityLevel.FATAL; - } + private TonlibJsonI tonlibJson; - if (isNull(super.keystorePath)) { - super.keystorePath = "."; - } + private Boolean printInfo; - if (isNull(super.liteServerIndex)) { - super.liteServerIndex = -1; - } - - super.keystorePath = super.keystorePath.replace("\\", "/"); + private static final Gson gson = + new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.BIG_DECIMAL).create(); - if (super.receiveRetryTimes == 0) { - super.receiveRetryTimes = 5; - } - - if (super.receiveTimeout == 0) { - super.receiveTimeout = 10.0; - } + private long tonlib; - if (isNull(super.ignoreCache)) { - super.ignoreCache = true; - } + RunResultParser runResultParser; - super.runResultParser = new RunResultParser(); - super.libraryResultParser = new LibraryResultParser(); + LibraryResultParser libraryResultParser; - if (isNull(super.pathToGlobalConfig)) { + public static class TonlibBuilder { + } - if (isNull(super.globalConfigAsString)) { - InputStream config; - if (super.testnet) { - super.pathToGlobalConfig = "testnet-global.config.json (integrated resource)"; - config = - Tonlib.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); - } else { - super.pathToGlobalConfig = "global-config.json (integrated resource)"; - config = Tonlib.class.getClassLoader().getResourceAsStream("global-config.json"); - } - super.originalGlobalConfigStr = Utils.streamToString(config); + public static TonlibBuilder builder() { + return new CustomTonlibBuilder(); + } - if (nonNull(config)) { - config.close(); + private static class CustomTonlibBuilder extends TonlibBuilder { + @Override + public Tonlib build() { + + try { + + if (isNull(super.printInfo)) { + super.printInfo = true; + } + + if (isNull(super.pathToTonlibSharedLib)) { + if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { + super.pathToTonlibSharedLib = Utils.detectAbsolutePath("tonlibjson", true); + } else { + super.pathToTonlibSharedLib = Utils.detectAbsolutePath("libtonlibjson", true); + } + } + + if (isNull(super.verbosityLevel)) { + super.verbosityLevel = VerbosityLevel.FATAL; + } + + if (isNull(super.keystorePath)) { + super.keystorePath = "."; + } + + if (isNull(super.liteServerIndex)) { + super.liteServerIndex = -1; + } + + super.keystorePath = super.keystorePath.replace("\\", "/"); + + if (super.receiveRetryTimes == 0) { + super.receiveRetryTimes = 5; + } + + if (super.receiveTimeout == 0) { + super.receiveTimeout = 10.0; + } + + if (isNull(super.ignoreCache)) { + super.ignoreCache = true; + } + + super.runResultParser = new RunResultParser(); + super.libraryResultParser = new LibraryResultParser(); + + if (isNull(super.pathToGlobalConfig)) { + + if (isNull(super.globalConfigAsString)) { + InputStream config; + if (super.testnet) { + super.pathToGlobalConfig = "testnet-global.config.json (integrated resource)"; + config = + Tonlib.class.getClassLoader().getResourceAsStream("testnet-global.config.json"); + } else { + super.pathToGlobalConfig = "global-config.json (integrated resource)"; + config = Tonlib.class.getClassLoader().getResourceAsStream("global-config.json"); + } + super.originalGlobalConfigStr = Utils.streamToString(config); + + if (nonNull(config)) { + config.close(); + } + } else { + super.originalGlobalConfigStr = super.globalConfigAsString; + } + } else if (nonNull(super.globalConfig)) { + super.originalGlobalConfigStr = gson.toJson(super.globalConfig); + } else { + if (Files.exists(Paths.get(super.pathToGlobalConfig))) { + super.originalGlobalConfigStr = + new String(Files.readAllBytes(Paths.get(super.pathToGlobalConfig))); + } else { + throw new RuntimeException( + "Global config is not found in path: " + super.pathToGlobalConfig); + } + } + + TonGlobalConfig globalConfigCurrent = + gson.fromJson(super.originalGlobalConfigStr, TonGlobalConfig.class); + super.originalGlobalConfigInternal = + gson.fromJson(super.originalGlobalConfigStr, TonGlobalConfig.class); + + if (super.liteServerIndex != -1) { + super.usingAllLiteServers = false; + if (super.liteServerIndex > globalConfigCurrent.getLiteservers().length - 1) { + throw new RuntimeException( + "Specified lite-server index is greater than total number of lite-servers in config."); + } + } else { + super.liteServerIndex = 0; + super.usingAllLiteServers = true; + } + + // always construct global config with one lite-server + // pick the first one if user hasn't specified any specific lite-server + // in case of error, the second lite-server from the original list of lite-servers will be + // picked + LiteServers[] liteServers = super.originalGlobalConfigInternal.getLiteservers(); + LiteServers[] newLiteServers = new LiteServers[1]; + newLiteServers[0] = liteServers[super.liteServerIndex]; + globalConfigCurrent.setLiteservers(newLiteServers); + + super.tonlibJson = Native.load(super.pathToTonlibSharedLib, TonlibJsonI.class); + + Utils.disableNativeOutput(); + + super.tonlib = super.tonlibJson.tonlib_client_json_create(); + + Utils.enableNativeOutput(); + + if (super.printInfo) { + log.info( + String.format( + "Java Tonlib configuration:\n" + + "Location: %s\n" + + "Verbosity level: %s (%s)\n" + + "Keystore in memory: %s\n" + + "Keystore path: %s\n" + + "Path to global config: %s\n" + + "Global config as string: %s\n" + + "lite-servers found: %s\n" + + "dht-nodes found: %s\n" + + "init-block seqno: %s\n" + + "%s\n" + + "Ignore cache: %s\n" + + "Testnet: %s\n" + + "Receive timeout: %s seconds\n" + + "Receive retry times: %s%n", + super.pathToTonlibSharedLib, + super.verbosityLevel, + super.verbosityLevel.ordinal(), + super.keystoreInMemory, + super.keystorePath, + super.pathToGlobalConfig, + (nonNull(super.globalConfigAsString) && super.globalConfigAsString.length() > 33) + ? super.globalConfigAsString.substring(0, 33) + : "", + super.originalGlobalConfigInternal.getLiteservers().length, + globalConfigCurrent.getDht().getStatic_nodes().getNodes().length, + globalConfigCurrent.getValidator().getInit_block().getSeqno(), + (super.usingAllLiteServers) + ? "using lite-servers: all" + : "using lite-server at index: " + + super.liteServerIndex + + " (" + + Utils.int2ip(globalConfigCurrent.getLiteservers()[0].getIp()) + + ")", + super.ignoreCache, + super.testnet, + super.receiveTimeout, + super.receiveRetryTimes)); + } + + // set verbosity + VerbosityLevelQuery verbosityLevelQuery = + VerbosityLevelQuery.builder() + .new_verbosity_level(super.verbosityLevel.ordinal()) + .build(); + + Utils.disableNativeOutput(); + super.tonlibJson.tonlib_client_json_send(super.tonlib, gson.toJson(verbosityLevelQuery)); + super.tonlibJson.tonlib_client_json_receive(super.tonlib, super.receiveTimeout); + Utils.enableNativeOutput(); + + initTonlibConfig(globalConfigCurrent); + + if (super.usingAllLiteServers) { + if (super.printInfo) { + log.info( + "Using lite-server at index: " + + (super.liteServerIndex) + + " (" + + Utils.int2ip(globalConfigCurrent.getLiteservers()[0].getIp()) + + ")"); + } + } + + } catch (Exception e) { + throw new RuntimeException("Error creating tonlib instance: " + e.getMessage()); } - } else { - super.originalGlobalConfigStr = super.globalConfigAsString; - } - } else if (nonNull(super.globalConfig)) { - super.originalGlobalConfigStr = gson.toJson(super.globalConfig); - } else { - if (Files.exists(Paths.get(super.pathToGlobalConfig))) { - super.originalGlobalConfigStr = - new String(Files.readAllBytes(Paths.get(super.pathToGlobalConfig))); - } else { - throw new RuntimeException( - "Global config is not found in path: " + super.pathToGlobalConfig); - } + return super.build(); } - TonGlobalConfig globalConfigCurrent = - gson.fromJson(super.originalGlobalConfigStr, TonGlobalConfig.class); - super.originalGlobalConfigInternal = - gson.fromJson(super.originalGlobalConfigStr, TonGlobalConfig.class); - - if (super.liteServerIndex != -1) { - super.usingAllLiteServers = false; - if (super.liteServerIndex > globalConfigCurrent.getLiteservers().length - 1) { - throw new RuntimeException( - "Specified lite-server index is greater than total number of lite-servers in config."); - } - } else { - super.liteServerIndex = 0; - super.usingAllLiteServers = true; + private void initTonlibConfig(TonGlobalConfig tonGlobalConfig) { + TonlibSetup tonlibSetup = + TonlibSetup.builder() + .type("init") + .options( + TonlibOptions.builder() + .type("options") + .config( + TonlibConfig.builder() + .type("config") + .config(gson.toJson(tonGlobalConfig)) + .use_callbacks_for_network(false) + .blockchain_name("") + .ignore_cache(super.ignoreCache) + .build()) + .keystore_type( + super.keystoreInMemory + ? KeyStoreTypeMemory.builder().type("keyStoreTypeInMemory").build() + : KeyStoreTypeDirectory.builder() + .type("keyStoreTypeDirectory") + .directory( + super.keystorePath.equals(".") ? "." : super.keystorePath) + .build()) + .build()) + .build(); + + Utils.disableNativeOutput(); + super.tonlibJson.tonlib_client_json_send(super.tonlib, gson.toJson(tonlibSetup)); + super.tonlibJson.tonlib_client_json_receive(super.tonlib, super.receiveTimeout); + Utils.enableNativeOutput(); } + } - // always construct global config with one lite-server - // pick the first one if user hasn't specified any specific lite-server - // in case of error, the second lite-server from the original list of lite-servers will be - // picked - LiteServers[] liteServers = super.originalGlobalConfigInternal.getLiteservers(); - LiteServers[] newLiteServers = new LiteServers[1]; - newLiteServers[0] = liteServers[super.liteServerIndex]; - globalConfigCurrent.setLiteservers(newLiteServers); + private void reinitTonlibConfig(TonGlobalConfig tonGlobalConfig) { - super.tonlibJson = Native.load(super.pathToTonlibSharedLib, TonlibJsonI.class); + // recreate tonlib instance + // tonlibJson.tonlib_client_json_destroy(tonlib); + destroy(); + tonlibJson = Native.load(pathToTonlibSharedLib, TonlibJsonI.class); Utils.disableNativeOutput(); - - super.tonlib = super.tonlibJson.tonlib_client_json_create(); - - Utils.enableNativeOutput(); - - if (super.printInfo) { - log.info( - String.format( - "Java Tonlib configuration:\n" - + "Location: %s\n" - + "Verbosity level: %s (%s)\n" - + "Keystore in memory: %s\n" - + "Keystore path: %s\n" - + "Path to global config: %s\n" - + "Global config as string: %s\n" - + "lite-servers found: %s\n" - + "dht-nodes found: %s\n" - + "init-block seqno: %s\n" - + "%s\n" - + "Ignore cache: %s\n" - + "Testnet: %s\n" - + "Receive timeout: %s seconds\n" - + "Receive retry times: %s%n", - super.pathToTonlibSharedLib, - super.verbosityLevel, - super.verbosityLevel.ordinal(), - super.keystoreInMemory, - super.keystorePath, - super.pathToGlobalConfig, - (nonNull(super.globalConfigAsString) && super.globalConfigAsString.length() > 33) - ? super.globalConfigAsString.substring(0, 33) - : "", - super.originalGlobalConfigInternal.getLiteservers().length, - globalConfigCurrent.getDht().getStatic_nodes().getNodes().length, - globalConfigCurrent.getValidator().getInit_block().getSeqno(), - (super.usingAllLiteServers) - ? "using lite-servers: all" - : "using lite-server at index: " - + super.liteServerIndex - + " (" - + Utils.int2ip(globalConfigCurrent.getLiteservers()[0].getIp()) - + ")", - super.ignoreCache, - super.testnet, - super.receiveTimeout, - super.receiveRetryTimes)); - } + tonlib = tonlibJson.tonlib_client_json_create(); // set verbosity VerbosityLevelQuery verbosityLevelQuery = - VerbosityLevelQuery.builder() - .new_verbosity_level(super.verbosityLevel.ordinal()) - .build(); + VerbosityLevelQuery.builder().new_verbosity_level(verbosityLevel.ordinal()).build(); + tonlibJson.tonlib_client_json_send(tonlib, gson.toJson(verbosityLevelQuery)); + tonlibJson.tonlib_client_json_receive(tonlib, receiveTimeout); + + TonlibSetup tonlibSetup = + TonlibSetup.builder() + .type("init") + .options( + TonlibOptions.builder() + .type("options") + .config( + TonlibConfig.builder() + .type("config") + .config(gson.toJson(tonGlobalConfig)) + .use_callbacks_for_network(false) + .blockchain_name("") + .ignore_cache(ignoreCache) + .build()) + .keystore_type( + keystoreInMemory + ? KeyStoreTypeMemory.builder().type("keyStoreTypeInMemory").build() + : KeyStoreTypeDirectory.builder() + .type("keyStoreTypeDirectory") + .directory(keystorePath.equals(".") ? "." : keystorePath) + .build()) + .build()) + .build(); + + tonlibJson.tonlib_client_json_send(tonlib, gson.toJson(tonlibSetup)); + tonlibJson.tonlib_client_json_receive(tonlib, receiveTimeout); - Utils.disableNativeOutput(); - super.tonlibJson.tonlib_client_json_send(super.tonlib, gson.toJson(verbosityLevelQuery)); - super.tonlibJson.tonlib_client_json_receive(super.tonlib, super.receiveTimeout); Utils.enableNativeOutput(); - - initTonlibConfig(globalConfigCurrent); - - if (super.usingAllLiteServers) { - if (super.printInfo) { - log.info( - "Using lite-server at index: " - + (super.liteServerIndex) - + " (" - + Utils.int2ip(globalConfigCurrent.getLiteservers()[0].getIp()) - + ")"); - } - } - - } catch (Exception e) { - throw new RuntimeException("Error creating tonlib instance: " + e.getMessage()); - } - return super.build(); - } - - private void initTonlibConfig(TonGlobalConfig tonGlobalConfig) { - TonlibSetup tonlibSetup = - TonlibSetup.builder() - .type("init") - .options( - TonlibOptions.builder() - .type("options") - .config( - TonlibConfig.builder() - .type("config") - .config(gson.toJson(tonGlobalConfig)) - .use_callbacks_for_network(false) - .blockchain_name("") - .ignore_cache(super.ignoreCache) - .build()) - .keystore_type( - super.keystoreInMemory - ? KeyStoreTypeMemory.builder().type("keyStoreTypeInMemory").build() - : KeyStoreTypeDirectory.builder() - .type("keyStoreTypeDirectory") - .directory( - super.keystorePath.equals(".") ? "." : super.keystorePath) - .build()) - .build()) - .build(); - - Utils.disableNativeOutput(); - super.tonlibJson.tonlib_client_json_send(super.tonlib, gson.toJson(tonlibSetup)); - super.tonlibJson.tonlib_client_json_receive(super.tonlib, super.receiveTimeout); - Utils.enableNativeOutput(); } - } - - private void reinitTonlibConfig(TonGlobalConfig tonGlobalConfig) { - // recreate tonlib instance - // tonlibJson.tonlib_client_json_destroy(tonlib); - destroy(); - - tonlibJson = Native.load(pathToTonlibSharedLib, TonlibJsonI.class); - Utils.disableNativeOutput(); - tonlib = tonlibJson.tonlib_client_json_create(); - - // set verbosity - VerbosityLevelQuery verbosityLevelQuery = - VerbosityLevelQuery.builder().new_verbosity_level(verbosityLevel.ordinal()).build(); - tonlibJson.tonlib_client_json_send(tonlib, gson.toJson(verbosityLevelQuery)); - tonlibJson.tonlib_client_json_receive(tonlib, receiveTimeout); - - TonlibSetup tonlibSetup = - TonlibSetup.builder() - .type("init") - .options( - TonlibOptions.builder() - .type("options") - .config( - TonlibConfig.builder() - .type("config") - .config(gson.toJson(tonGlobalConfig)) - .use_callbacks_for_network(false) - .blockchain_name("") - .ignore_cache(ignoreCache) - .build()) - .keystore_type( - keystoreInMemory - ? KeyStoreTypeMemory.builder().type("keyStoreTypeInMemory").build() - : KeyStoreTypeDirectory.builder() - .type("keyStoreTypeDirectory") - .directory(keystorePath.equals(".") ? "." : keystorePath) - .build()) - .build()) - .build(); - - tonlibJson.tonlib_client_json_send(tonlib, gson.toJson(tonlibSetup)); - tonlibJson.tonlib_client_json_receive(tonlib, receiveTimeout); - - Utils.enableNativeOutput(); - } - - public void destroy() { - Utils.disableNativeOutput(); - tonlibJson.tonlib_client_json_destroy(tonlib); - Utils.enableNativeOutput(); - } - - private String receive() { - String result = null; - int retry = 0; - while (isNull(result)) { - if (retry > 0) { -// log.info("retry " + retry); - } - if (++retry > receiveRetryTimes) { - throw new Error( - "Error in tonlib.receive(), " - + receiveRetryTimes - + " times was not able retrieve result from lite-server."); - } -// Utils.disableNativeOutput(); - result = tonlibJson.tonlib_client_json_receive(tonlib, receiveTimeout); -// Utils.enableNativeOutput(); + public void destroy() { + Utils.disableNativeOutput(); + tonlibJson.tonlib_client_json_destroy(tonlib); + Utils.enableNativeOutput(); } - return result; - } - - private String syncAndRead(String query) { - String response = null; - try { - Utils.disableNativeOutput(); - tonlibJson.tonlib_client_json_send(tonlib, query); - TimeUnit.MILLISECONDS.sleep(200); - response = receive(); - Utils.enableNativeOutput(); - int retry = 0; - outterloop: - do { - do { - - if (response.contains("error")) { - log.info(response); + private String receive() { + String result = null; + int retry = 0; + while (isNull(result)) { + // if (retry > 0) { + // log.info("retry " + retry); + // } if (++retry > receiveRetryTimes) { - throw new Error( - "Error in tonlib.receive(), " - + receiveRetryTimes - + " times was not able retrieve result from lite-server."); + throw new Error( + "Error in tonlib.receive(), " + + receiveRetryTimes + + " times was not able retrieve result from lite-server."); } + result = tonlibJson.tonlib_client_json_receive(tonlib, receiveTimeout); + } + return result; + } - if (response.contains("Failed to unpack account state")) { - log.info( - "You are trying to deploy a contract on address that does not have toncoins."); - break outterloop; - } + private String syncAndRead(String query) { + String response = null; + try { + Utils.disableNativeOutput(); + tonlibJson.tonlib_client_json_send(tonlib, query); + TimeUnit.MILLISECONDS.sleep(200); + response = receive(); + Utils.enableNativeOutput(); + int retry = 0; + outterloop: + do { + do { + + if (response.contains("error")) { + log.info(response); + + if (++retry > receiveRetryTimes) { + throw new Error( + "Error in tonlib.receive(), " + + receiveRetryTimes + + " times was not able retrieve result from lite-server."); + } + + if (response.contains("Failed to unpack account state")) { + log.info( + "You are trying to deploy a contract on address that does not have toncoins."); + break outterloop; + } + + if (usingAllLiteServers) { + // try next lite-server from the list + TonGlobalConfig globalConfigCurrent = + gson.fromJson(originalGlobalConfigStr, TonGlobalConfig.class); + LiteServers[] liteServers = originalGlobalConfigInternal.getLiteservers(); + LiteServers[] newLiteServers = new LiteServers[1]; + newLiteServers[0] = + liteServers[retry % originalGlobalConfigInternal.getLiteservers().length]; + globalConfigCurrent.setLiteservers(newLiteServers); + + log.info( + "Trying next lite-server at index: " + + (retry % originalGlobalConfigInternal.getLiteservers().length) + + " (" + + Utils.int2ip(globalConfigCurrent.getLiteservers()[0].getIp()) + + ")"); + + reinitTonlibConfig(globalConfigCurrent); + // repeat request + Utils.disableNativeOutput(); + tonlibJson.tonlib_client_json_send(tonlib, query); + Utils.enableNativeOutput(); + } + } else if (response.contains("\"@type\":\"ok\"")) { + String queryExtraId = StringUtils.substringBetween(query, "@extra\":\"", "\"}"); + String responseExtraId = StringUtils.substringBetween(response, "@extra\":\"", "\"}"); + if (queryExtraId.equals(responseExtraId)) { + break outterloop; + } + } else if (response.contains("\"@extra\"")) { + break outterloop; + } + + if (response.contains(" : duplicate message\"")) { + break outterloop; + } + Utils.disableNativeOutput(); + TimeUnit.MILLISECONDS.sleep(200); + response = receive(); + Utils.enableNativeOutput(); + UpdateSyncState sync = gson.fromJson(response, UpdateSyncState.class); + if (nonNull(sync) + && nonNull(sync.getSync_state()) + && sync.getType().equals("updateSyncState") + && !response.contains("syncStateDone")) { + double pct = 0.0; + if (sync.getSync_state().getTo_seqno() != 0) { + pct = + (sync.getSync_state().getCurrent_seqno() * 100) + / (double) sync.getSync_state().getTo_seqno(); + } + if (pct < 99.5) { + log.info("Synchronized: " + String.format("%.2f%%", pct)); + } + } + if (isNull(response)) { + throw new RuntimeException("Error in waitForSyncDone(), response is null."); + } + + } while (response.contains("error") || response.contains("syncStateInProgress")); + + if (response.contains("syncStateDone")) { + response = receive(); + } + if (response.contains("error")) { + log.info(response); + + if (++retry > receiveRetryTimes) { + throw new Error( + "Error in tonlib.receive(), " + + receiveRetryTimes + + " times was not able retrieve result from lite-server."); + } + + Utils.disableNativeOutput(); + tonlibJson.tonlib_client_json_send(tonlib, query); + Utils.enableNativeOutput(); + } + } while (response.contains("error") || response.contains("syncStateInProgress")); + + return response; - if (usingAllLiteServers) { - // try next lite-server from the list - TonGlobalConfig globalConfigCurrent = - gson.fromJson(originalGlobalConfigStr, TonGlobalConfig.class); - LiteServers[] liteServers = originalGlobalConfigInternal.getLiteservers(); - LiteServers[] newLiteServers = new LiteServers[1]; - newLiteServers[0] = - liteServers[retry % originalGlobalConfigInternal.getLiteservers().length]; - globalConfigCurrent.setLiteservers(newLiteServers); - - log.info( - "Trying next lite-server at index: " - + (retry % originalGlobalConfigInternal.getLiteservers().length) - + " (" - + Utils.int2ip(globalConfigCurrent.getLiteservers()[0].getIp()) - + ")"); - - reinitTonlibConfig(globalConfigCurrent); - // repeat request - Utils.disableNativeOutput(); - tonlibJson.tonlib_client_json_send(tonlib, query); - Utils.enableNativeOutput(); - } - } else if (response.contains("\"@type\":\"ok\"")) { - String queryExtraId = StringUtils.substringBetween(query, "@extra\":\"", "\"}"); - String responseExtraId = StringUtils.substringBetween(response, "@extra\":\"", "\"}"); - if (queryExtraId.equals(responseExtraId)) { - break outterloop; - } - } else if (response.contains("\"@extra\"")) { - break outterloop; - } + } catch (Exception e) { + log.info(e.getMessage()); + return response; + } + } - if (response.contains(" : duplicate message\"")) { - break outterloop; - } - Utils.disableNativeOutput(); - TimeUnit.MILLISECONDS.sleep(200); - response = receive(); - Utils.enableNativeOutput(); - UpdateSyncState sync = gson.fromJson(response, UpdateSyncState.class); - if (nonNull(sync) - && nonNull(sync.getSync_state()) - && sync.getType().equals("updateSyncState") - && !response.contains("syncStateDone")) { - double pct = 0.0; - if (sync.getSync_state().getTo_seqno() != 0) { - pct = - (sync.getSync_state().getCurrent_seqno() * 100) - / (double) sync.getSync_state().getTo_seqno(); + /** + * Get BlockIdExt by parameters. + * + * @param seqno long, can be zero if unknown + * @param workchain long, -1 or 0 + * @param shard long + * @param lt long + * @param utime long + * @return BlockIdExt + */ + public BlockIdExt lookupBlock(long seqno, long workchain, long shard, long lt, long utime) { + synchronized (gson) { + int mode = 0; + if (seqno != 0) { + mode += 1; } - if (pct < 99.5) { - log.info("Synchronized: " + String.format("%.2f%%", pct)); + if (lt != 0) { + mode += 2; } - } - if (isNull(response)) { - throw new RuntimeException("Error in waitForSyncDone(), response is null."); - } - - } while (response.contains("error") || response.contains("syncStateInProgress")); - - if (response.contains("syncStateDone")) { - response = receive(); - } - if (response.contains("error")) { - log.info(response); - - if (++retry > receiveRetryTimes) { - throw new Error( - "Error in tonlib.receive(), " - + receiveRetryTimes - + " times was not able retrieve result from lite-server."); - } - - Utils.disableNativeOutput(); - tonlibJson.tonlib_client_json_send(tonlib, query); - Utils.enableNativeOutput(); + if (utime != 0) { + mode += 4; + } + LookupBlockQuery lookupBlockQuery = + LookupBlockQuery.builder() + .mode(mode) + .id(BlockId.builder().seqno(seqno).workchain(workchain).shard(shard).build()) + .lt(lt) + .utime(utime) + .build(); + + String result = syncAndRead(gson.toJson(lookupBlockQuery)); + return gson.fromJson(result, BlockIdExt.class); } - } while (response.contains("error") || response.contains("syncStateInProgress")); - - return response; - - } catch (Exception e) { - log.info(e.getMessage()); - return response; } - } - public BlockIdExt lookupBlock(long seqno, long workchain, long shard, long lt, long utime) { - synchronized (gson) { - int mode = 0; - if (seqno != 0) { - mode += 1; - } - if (lt != 0) { - mode += 2; - } - if (utime != 0) { - mode += 4; - } - LookupBlockQuery lookupBlockQuery = - LookupBlockQuery.builder() - .mode(mode) - .id(BlockId.builder().seqno(seqno).workchain(workchain).shard(shard).build()) - .lt(lt) - .utime(utime) - .build(); - - String result = syncAndRead(gson.toJson(lookupBlockQuery)); - return gson.fromJson(result, BlockIdExt.class); + public BlockIdExt lookupBlock(long seqno, long workchain, long shard, long lt) { + return lookupBlock(seqno, workchain, shard, lt, 0); } - } - public BlockIdExt lookupBlock(long seqno, long workchain, long shard, long lt) { - return lookupBlock(seqno, workchain, shard, lt, 0); - } - - public MasterChainInfo getLast() { - synchronized (gson) { - GetLastQuery getLastQuery = GetLastQuery.builder().build(); + public MasterChainInfo getLast() { + synchronized (gson) { + GetLastQuery getLastQuery = GetLastQuery.builder().build(); - String result = syncAndRead(gson.toJson(getLastQuery)); - return gson.fromJson(result, MasterChainInfo.class); + String result = syncAndRead(gson.toJson(getLastQuery)); + return gson.fromJson(result, MasterChainInfo.class); + } } - } - public MasterChainInfo getMasterChainInfo() { - return getLast(); - } + public MasterChainInfo getMasterChainInfo() { + return getLast(); + } - public Shards getShards(BlockIdExt id) { - synchronized (gson) { - GetShardsQuery getShardsQuery = GetShardsQuery.builder().id(id).build(); + public Shards getShards(BlockIdExt id) { + synchronized (gson) { + GetShardsQuery getShardsQuery = GetShardsQuery.builder().id(id).build(); - String result = syncAndRead(gson.toJson(getShardsQuery)); - return gson.fromJson(result, Shards.class); + String result = syncAndRead(gson.toJson(getShardsQuery)); + return gson.fromJson(result, Shards.class); + } } - } - public Shards getShards(long seqno, long lt, long unixtime) { - if ((seqno <= 0) && (lt <= 0) && (unixtime <= 0)) { - throw new Error("Seqno, LT or unixtime should be defined"); - } + public Shards getShards(long seqno, long lt, long unixtime) { + if ((seqno <= 0) && (lt <= 0) && (unixtime <= 0)) { + throw new Error("Seqno, LT or unixtime should be defined"); + } - long wc = -1; - long shard = -9223372036854775808L; + long wc = -1; + long shard = -9223372036854775808L; - BlockIdExt fullblock = lookupBlock(seqno, wc, shard, lt, unixtime); + BlockIdExt fullblock = lookupBlock(seqno, wc, shard, lt, unixtime); - synchronized (gson) { - GetShardsQuery getShardsQuery = GetShardsQuery.builder().id(fullblock).build(); + synchronized (gson) { + GetShardsQuery getShardsQuery = GetShardsQuery.builder().id(fullblock).build(); - String result = syncAndRead(gson.toJson(getShardsQuery)); - return gson.fromJson(result, Shards.class); + String result = syncAndRead(gson.toJson(getShardsQuery)); + return gson.fromJson(result, Shards.class); + } } - } - public Key createNewKey() { - synchronized (gson) { - NewKeyQuery newKeyQuery = NewKeyQuery.builder().build(); + public Key createNewKey() { + synchronized (gson) { + NewKeyQuery newKeyQuery = NewKeyQuery.builder().build(); - String result = syncAndRead(gson.toJson(newKeyQuery)); - return gson.fromJson(result, Key.class); + String result = syncAndRead(gson.toJson(newKeyQuery)); + return gson.fromJson(result, Key.class); + } } - } - public Data encrypt(String data, String secret) { - synchronized (gson) { - EncryptQuery encryptQuery = - EncryptQuery.builder().decrypted_data(data).secret(secret).build(); + public Data encrypt(String data, String secret) { + synchronized (gson) { + EncryptQuery encryptQuery = + EncryptQuery.builder().decrypted_data(data).secret(secret).build(); - String result = syncAndRead(gson.toJson(encryptQuery)); - return gson.fromJson(result, Data.class); + String result = syncAndRead(gson.toJson(encryptQuery)); + return gson.fromJson(result, Data.class); + } } - } - public Data decrypt(String data, String secret) { - synchronized (gson) { - DecryptQuery decryptQuery = - DecryptQuery.builder().encrypted_data(data).secret(secret).build(); + public Data decrypt(String data, String secret) { + synchronized (gson) { + DecryptQuery decryptQuery = + DecryptQuery.builder().encrypted_data(data).secret(secret).build(); - String result = syncAndRead(gson.toJson(decryptQuery)); - return gson.fromJson(result, Data.class); + String result = syncAndRead(gson.toJson(decryptQuery)); + return gson.fromJson(result, Data.class); + } } - } - public BlockHeader getBlockHeader(BlockIdExt fullblock) { - synchronized (gson) { - BlockHeaderQuery blockHeaderQuery = BlockHeaderQuery.builder().id(fullblock).build(); + public BlockHeader getBlockHeader(BlockIdExt fullblock) { + synchronized (gson) { + BlockHeaderQuery blockHeaderQuery = BlockHeaderQuery.builder().id(fullblock).build(); - String result = syncAndRead(gson.toJson(blockHeaderQuery)); - return gson.fromJson(result, BlockHeader.class); + String result = syncAndRead(gson.toJson(blockHeaderQuery)); + return gson.fromJson(result, BlockHeader.class); + } } - } - // @formatter:off + // @formatter:off /** * @param address String @@ -1492,6 +1511,24 @@ public RawTransaction tryLocateTxByIncomingMessage( throw new Error("Transaction not found"); } + /** + * PR pending https://github.com/ton-blockchain/ton/pull/1379 todo + * + * @param address + * @param msgHexHash + * @return + */ + private RawTransaction getTxByMessageHash(String address, String msgHexHash) { + RawTransactions rawTransactions = getRawTransactions(address, null, null); + for (RawTransaction tx : rawTransactions.getTransactions()) { + if (nonNull(tx.getIn_msg())) { +// if (tx.getIn_msg().hashCode()) { +// } + } + } + return null; + } + public RawTransaction getRawTransaction(byte workchain, ShortTxId tx) { String addressHex = Utils.base64ToHexString(tx.getAccount()); String address = Address.of(workchain + ":" + addressHex).toString(false); @@ -1607,7 +1644,7 @@ public boolean isDeployed(Address address) { public void waitForDeployment(Address address, int timeoutSeconds) { log.info( - "Waiting for deployment (up to {}s) - {} - ({})", + "Waiting for deployment (up to {}s) - {} ({})", timeoutSeconds, testnet ? address.toBounceableTestnet() : address.toBounceable(), address.toRaw()); @@ -1622,7 +1659,7 @@ public void waitForDeployment(Address address, int timeoutSeconds) { public void waitForBalanceChange(Address address, int timeoutSeconds) { log.info( - "Waiting for balance change (up to {}s) - {} - ({})", + "Waiting for balance change (up to {}s) - {} ({})", timeoutSeconds, testnet ? address.toBounceableTestnet() : address.toBounceable(), address.toRaw()); diff --git a/tonlib/src/main/java/org/ton/java/tonlib/types/BlockIdExt.java b/tonlib/src/main/java/org/ton/java/tonlib/types/BlockIdExt.java index f9e8612b..b74a75ed 100644 --- a/tonlib/src/main/java/org/ton/java/tonlib/types/BlockIdExt.java +++ b/tonlib/src/main/java/org/ton/java/tonlib/types/BlockIdExt.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.ton.java.utils.Utils; import java.io.Serializable; @@ -13,6 +14,8 @@ @Getter @ToString public class BlockIdExt implements Serializable { + + @SerializedName("@type") final String type = "ton.blockIdExt"; long workchain; @@ -20,5 +23,13 @@ public class BlockIdExt implements Serializable { long seqno; String root_hash; String file_hash; + + public String getShortBlockSeqno() { + return String.format("(%d,%s,%d)", workchain, Utils.longToUnsignedBigInteger(shard).toString(16), seqno); + } + + public String getFullBlockSeqno() { + return String.format("(%d,%s,%d):%s:%s", workchain, Utils.longToUnsignedBigInteger(shard).toString(16), seqno, root_hash, file_hash); + } } diff --git a/tonlib/src/main/java/org/ton/java/tonlib/types/RawMessage.java b/tonlib/src/main/java/org/ton/java/tonlib/types/RawMessage.java index 0b2f5155..ae436f63 100644 --- a/tonlib/src/main/java/org/ton/java/tonlib/types/RawMessage.java +++ b/tonlib/src/main/java/org/ton/java/tonlib/types/RawMessage.java @@ -2,9 +2,7 @@ import com.google.gson.annotations.SerializedName; import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; +import lombok.Data; import org.apache.commons.codec.binary.Hex; import org.ton.java.cell.Cell; import org.ton.java.cell.CellSlice; @@ -15,12 +13,11 @@ import static java.util.Objects.nonNull; @Builder -@Setter -@Getter -@ToString +@Data public class RawMessage implements Serializable { @SerializedName("@type") final String type = "raw.message"; + AccountAddressOnly source; AccountAddressOnly destination; String value; @@ -31,8 +28,8 @@ public class RawMessage implements Serializable { MsgData msg_data; /** - * Returns base64 result which is encoded in base64 by default. - * This is a wrapper around msg_data.getBody(), but additionally decodes text message from base64 to plain string. + * Returns base64 result which is encoded in base64 by default. This is a wrapper around + * msg_data.getBody(), but additionally decodes text message from base64 to plain string. * * @return String */ @@ -72,9 +69,10 @@ public String getMessageHex() { public String getComment() { if (nonNull(msg_data.getText())) { - return CellSlice.beginParse(Cell.fromHex(Utils.base64ToHexString(msg_data.getText()))).loadSnakeString(); + return CellSlice.beginParse(Cell.fromHex(Utils.base64ToHexString(msg_data.getText()))) + .loadSnakeString(); } else { return ""; } } -} \ No newline at end of file +} diff --git a/tonlib/src/main/java/org/ton/java/tonlib/types/RawTransactions.java b/tonlib/src/main/java/org/ton/java/tonlib/types/RawTransactions.java index 441468df..88c584c7 100644 --- a/tonlib/src/main/java/org/ton/java/tonlib/types/RawTransactions.java +++ b/tonlib/src/main/java/org/ton/java/tonlib/types/RawTransactions.java @@ -2,17 +2,13 @@ import com.google.gson.annotations.SerializedName; import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; +import lombok.Data; import java.io.Serializable; import java.util.List; @Builder -@Setter -@Getter -@ToString +@Data public class RawTransactions implements Serializable { @SerializedName("@type") final String type = "raw.transactions"; 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 ce00c20c..22cbceb6 100644 --- a/tonlib/src/test/java/org/ton/java/tonlib/TestTonlibJson.java +++ b/tonlib/src/test/java/org/ton/java/tonlib/TestTonlibJson.java @@ -30,6 +30,7 @@ import org.ton.java.cell.CellSlice; import org.ton.java.mnemonic.Mnemonic; import org.ton.java.tlb.types.ConfigParams8; +import org.ton.java.tlb.types.Transaction; import org.ton.java.tonlib.types.*; import org.ton.java.tonlib.types.globalconfig.*; import org.ton.java.utils.Utils; @@ -358,6 +359,8 @@ public void testTonlibGetTxsWithLimitByAddress() { RawTransactions rawTransactions = tonlib.getRawTransactions(address.toRaw(), null, null, 3); for (RawTransaction tx : rawTransactions.getTransactions()) { + Transaction transaction = Transaction.deserialize(CellSlice.beginParse(CellBuilder.beginCell().fromBocBase64(tx.getData()).endCell())); + log.info("transaction {}", transaction); if (nonNull(tx.getIn_msg()) && (!tx.getIn_msg().getSource().getAccount_address().equals(""))) { log.info( @@ -957,4 +960,60 @@ public void testTonlibGetLibraries() { assertThat(result.getResult().get(0).getHash()) .isEqualTo("wkUmK4wrzl6fzSPKM04dVfqW1M5pqigX3tcXzvy6P3M="); } + + @Test + public void testTonlibRunMethodGetSrcAddr() { + Tonlib tonlib = Tonlib.builder().testnet(false).ignoreCache(false).build(); + Address smc = Address.of("EQD-BJSVUJviud_Qv7Ymfd3qzXdrmV525e3YDzWQoHIAiInL"); + + Deque stack = new ArrayDeque<>(); + BigInteger arg1 = + new BigInteger("6aaf20fed58ba5e6db692909e78e5c5c6525e28d1cfa8bd22dc216729b4841b3", 16); + BigInteger arg2 = + new BigInteger("e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f3412", 16); + stack.offer("[num," + arg1 + "]"); + stack.offer("[num," + arg2 + "]"); + + RunResult result = tonlib.runMethod(smc, "get_source_item_address", stack); + log.info("result {}", result); + TvmStackEntrySlice slice = ((TvmStackEntrySlice) result.getStack().get(0)); + String b64 = Utils.base64ToHexString(slice.getSlice().getBytes()); + log.info("b64 {}", b64); + + // @formatter:off + // tonlib + // 4 4 desc slice crc + // b5ee9c72410101010024 00 ---- 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 7111e8d8 + Cell c = CellBuilder.beginCell().fromBoc(b64).endCell(); + log.info("c {}", c.toHex(true)); + + // b5ee9c72010101010024 00 00 43801f0111d15a69c7d42a36433173c773ce24bf905542d3d38dd073462b4a5b136c30 subbotin-1 + // b5ee9c72010101010024 00 00 43800f2034101a96276a66c408417b1d38f5fcb3d6187226930600e8273b43c92a6970 subbotin-2 + + // slice returned by lite-client + // "0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0" + + // ton4j to boc 6 6 + // b5ee9c72410101010024 00 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 7111e8d8 tonlib + // + // 4 + // b5ee9c72410101010024 00 0048 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 cad13166 with crc + store all bytes + // b5ee9c72410101010026 00 0048 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 cad13166 crc + store 267 bits + // b5ee9c72c10101010026 00 2600 480043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 fe87f4b3 crc+index + // b5ee9c72010101010026 00 6048 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 pruned + // b5ee9c72010101010026 00 0048 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 ordinary + // b5ee9c72010101010026 00 0048 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 library + // go lang + // b5ee9c72410101010024 00 0043 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3 fd0 d197c327 store 267 bits + // b5ee9c72410101010026 00 0048 0043801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 cad13166 + // pytoniq-core + // + // slice returned by lite-client and trimmed front + // "801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0" + // 4 4 + // b5ee9c72410101010024 00 0044 801482bf04d1769cf0b59f5ffd4cbf659b5e1d9ddd2eccc47901ec29242b3fd76fb0 98f3b93d + + + // @formatter:on + } } diff --git a/utils/src/main/java/org/ton/java/utils/Utils.java b/utils/src/main/java/org/ton/java/utils/Utils.java index 33ea9a24..ba658e53 100644 --- a/utils/src/main/java/org/ton/java/utils/Utils.java +++ b/utils/src/main/java/org/ton/java/utils/Utils.java @@ -1,6 +1,14 @@ package org.ton.java.utils; import com.iwebpp.crypto.TweetNaclFast; +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import java.io.*; import java.lang.reflect.Field; @@ -22,1146 +30,1177 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinNT; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; +import static java.util.Objects.isNull; public class Utils { - private static final Logger log = Logger.getLogger(Utils.class.getName()); - private static final String HEXES = "0123456789ABCDEF"; - private static final long BLN1 = 1000000000L; - private static final BigInteger BI_BLN1 = BigInteger.valueOf(BLN1); - private static final BigDecimal BD_BLN1 = BigDecimal.valueOf(BLN1); - - public enum OS { - WINDOWS, - WINDOWS_ARM, - LINUX, - LINUX_ARM, - MAC, - MAC_ARM64, - UNKNOWN - } - - /** uses POLY 0x1EDC6F41 */ - public static Long getCRC32ChecksumAsLong(byte[] bytes) { - CRC32C crc32c = new CRC32C(); - crc32c.update(bytes, 0, bytes.length); - return crc32c.getValue() & 0x00000000ffffffffL; - } - - public static String getCRC32ChecksumAsHex(byte[] bytes) { - return BigInteger.valueOf(getCRC32ChecksumAsLong(bytes)).toString(16); - } - - public static byte[] getCRC32ChecksumAsBytes(byte[] bytes) { - return long4BytesToBytes(getCRC32ChecksumAsLong(bytes)); - } - - public static byte[] getCRC32ChecksumAsBytesReversed(byte[] bytes) { - byte[] b = long4BytesToBytes(getCRC32ChecksumAsLong(bytes)); - - byte[] reversed = new byte[4]; - reversed[0] = b[3]; - reversed[1] = b[2]; - reversed[2] = b[1]; - reversed[3] = b[0]; - - return reversed; - } - - /** - * Long to signed bytes - * - * @param l value - * @return array of unsigned bytes - */ - public static byte[] long4BytesToBytes(long l) { - byte[] result = new byte[4]; - for (int i = 3; i >= 0; i--) { - result[i] = (byte) (l & 0xFF); - l >>= 8; - } - return result; - } - - public static int[] longToBytes(long l) { - int[] result = new int[8]; - for (int i = 7; i >= 0; i--) { - result[i] = (int) l & 0xFF; - l >>= 8; - } - return result; - } - - public static long bytesToLong(final byte[] b) { - long result = 0; - for (int i = 0; i < 8; i++) { - result <<= 8; - result |= (b[i] & 0xFF); - } - return result; - } - - public static int bytesToInt(final byte[] b) { - int result = 0; - for (int i = 0; i < 4; i++) { - result <<= 8; - result |= (b[i] & 0xFF); - } - return result; - } - - public static int bytesToIntX(final byte[] b) { - int result = 0; - for (byte value : b) { - result <<= 8; - result |= value & 0XFF; - } - return result; - } - - public static short bytesToShort(final byte[] b) { - short result = 0; - for (int i = 0; i < 2; i++) { - result <<= 8; - result |= (short) (b[i] & 0xFF); - } - return result; - } - - public static long intsToLong(final int[] b) { - long result = 0; - for (int i = 0; i < 8; i++) { - result <<= 8; - result |= b[i]; - } - return result; - } - - public static int intsToInt(final int[] b) { - int result = 0; - for (int i = 0; i < 4; i++) { - result <<= 8; - result |= b[i]; - } - return result; - } - - public static short intsToShort(final int[] b) { - short result = 0; - for (int i = 0; i < 2; i++) { - result <<= 8; - result |= (short) b[i]; - } - return result; - } - - public static int[] intToIntArray(int l) { - return new int[] {l}; - } - - public static byte[] intToByteArray(int value) { - return new byte[] {(byte) (value >>> 8), (byte) value}; - } - - // CRC-16/XMODEM - public static int getCRC16ChecksumAsInt(byte[] bytes) { - int crc = 0x0000; - int polynomial = 0x1021; - - for (byte b : bytes) { - for (int i = 0; i < 8; i++) { - boolean bit = ((b >> (7 - i) & 1) == 1); - boolean c15 = ((crc >> 15 & 1) == 1); - crc <<= 1; - if (c15 ^ bit) crc ^= polynomial; - } - } - - crc &= 0xffff; - return crc; - } - - public static int calculateMethodId(String methodName) { - int l = Utils.getCRC16ChecksumAsInt(methodName.getBytes()); - l = (l & 0xffff) | 0x10000; - return l; - } - - public static String getCRC16ChecksumAsHex(byte[] bytes) { - return bytesToHex(getCRC16ChecksumAsBytes(bytes)); - } - - public static byte[] getCRC16ChecksumAsBytes(byte[] bytes) { - return intToByteArray(getCRC16ChecksumAsInt(bytes)); - } - - public static String sha256(final String base) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - final byte[] hash = digest.digest(base.getBytes(StandardCharsets.UTF_8)); - final StringBuilder hexString = new StringBuilder(); - for (byte b : hash) { - final String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) hexString.append('0'); - hexString.append(hex); - } - return hexString.toString(); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - public static String sha256(int[] bytes) { - byte[] converted = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - converted[i] = (byte) (bytes[i] & 0xff); - } - return sha256(converted); - } - - public static byte[] unsignedBytesToSigned(int[] bytes) { - byte[] converted = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - converted[i] = (byte) (bytes[i] & 0xff); - } - return converted; - } - - public static int[] signedBytesToUnsigned(byte[] bytes) { - int[] converted = new int[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - converted[i] = Byte.toUnsignedInt(bytes[i]); - } - return converted; - } - - public static byte[] sha256AsArray(byte[] bytes) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return digest.digest(bytes); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - - public static byte[] sha1AsArray(byte[] bytes) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-1"); - return digest.digest(bytes); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - - public static byte[] md5AsArray(byte[] bytes) { - try { - final MessageDigest digest = MessageDigest.getInstance("MD5"); - return digest.digest(bytes); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - - public static String md5(byte[] bytes) { - try { - final MessageDigest digest = MessageDigest.getInstance("MD5"); - final byte[] hash = digest.digest(bytes); - return Utils.bytesToHex(hash); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - - public static String sha256(byte[] bytes) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-256"); - final byte[] hash = digest.digest(bytes); - return Utils.bytesToHex(hash); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - - public static String sha1(byte[] bytes) { - try { - final MessageDigest digest = MessageDigest.getInstance("SHA-1"); - final byte[] hash = digest.digest(bytes); - return Utils.bytesToHex(hash); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException(ex); - } - } - - public static String bitsToDec(boolean[] bits) { - StringBuilder s = new StringBuilder(); - for (boolean b : bits) { - s.append(b ? '1' : '0'); - } - return new BigInteger(s.toString(), 2).toString(10); - } - - public static String bitsToHex(boolean[] bits) { - StringBuilder s = new StringBuilder(); - for (boolean b : bits) { - s.append(b ? '1' : '0'); - } - return new BigInteger(s.toString(), 2).toString(16); - } - - public static String bytesToBitString(byte[] raw) { - String hex = Utils.bytesToHex(signedBytesToUnsigned(raw)); - BigInteger bi = new BigInteger(hex, 16); - return bi.toString(2); - } - - public static String bytesToBitString(int[] raw) { - String hex = Utils.bytesToHex(raw); - BigInteger bi = new BigInteger(hex, 16); - return bi.toString(2); - } - - public static String bytesToHex(byte[] raw) { - final StringBuilder hex = new StringBuilder(2 * raw.length); - for (final byte b : raw) { - hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); - } - return hex.toString().toLowerCase(); - } - - public static String bytesToHex(int[] raw) { - final StringBuilder hex = new StringBuilder(2 * raw.length); - for (final int b : raw) { - hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); - } - return hex.toString().toLowerCase(); - } - - public static String base64UrlSafeToHexString(String base64) { - byte[] decoded = Base64.getUrlDecoder().decode(base64); - return bytesToHex(decoded); - } - - public static String base64ToHexString(String base64) { - byte[] decoded = Base64.getDecoder().decode(base64); - return bytesToHex(decoded); - } - - public static String hexStringToBase64UrlSafe(String hex) throws DecoderException { - byte[] decodedHex = Hex.decodeHex(hex); - return new String(Base64.getUrlEncoder().encode(decodedHex)); - } - - public static String hexStringToBase64(String hex) throws DecoderException { - byte[] decodedHex = Hex.decodeHex(hex); - return new String(Base64.getEncoder().encode(decodedHex)); - } - - public static String base64ToBitString(String base64) { - byte[] decode = Base64.getDecoder().decode(base64); - return new BigInteger(1, decode).toString(2); - } - - public static String bytesToBase64(byte[] bytes) { - return Base64.getEncoder().encodeToString(bytes); - } - - public static String bytesToBase64(int[] bytes) { - return Base64.getEncoder().encodeToString(Utils.unsignedBytesToSigned(bytes)); - } - - public static String bytesToBase64SafeUrl(byte[] bytes) { - return Base64.getUrlEncoder().encodeToString(bytes); - } - - public static byte[] base64ToBytes(String base64) { - return Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); - } - - public static int[] base64ToUnsignedBytes(String base64) { - return Utils.signedBytesToUnsigned( - Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8))); - } - - public static byte[] base64ToSignedBytes(String base64) { - return Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); - } - - public static byte[] base64SafeUrlToBytes(String base64) { - return Base64.getUrlDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); - } - - public static String base64ToString(String base64) { - return new String(Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8))); - } - - public static String stringToBase64(String str) { - return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)); - } - - public static String bitStringToHex(String binary) { - int toPad = (binary.length() % 8) == 0 ? 0 : 8 - (binary.length() % 8); - final StringBuilder bits = new StringBuilder(binary); - if (toPad != 0) { - for (int i = 0; i < toPad; i++) { - bits.append('0'); - } - } - return new BigInteger(bits.toString(), 2).toString(16); - } - - public static String bitStringToBase64(String binary) throws DecoderException { - int toPad = (binary.length() % 8) == 0 ? 0 : 8 - (binary.length() % 8); - final StringBuilder bits = new StringBuilder(binary); - if (toPad != 0) { - for (int i = 0; i < toPad; i++) { - bits.append('0'); - } - } - String hex = new BigInteger(bits.toString(), 2).toString(16); - byte[] decodedHex = Hex.decodeHex(hex); - return new String(Base64.getEncoder().encode(decodedHex)); - } - - public static String repeat(String str, int count) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < count; i++) { - sb.append(str); - } - return sb.toString(); - } - - public static String bitStringToBase64UrlSafe(String binary) throws DecoderException { - int toPad = (binary.length() % 8) == 0 ? 0 : 8 - (binary.length() % 8); - final StringBuilder bits = new StringBuilder(binary); - if (toPad != 0) { - for (int i = 0; i < toPad; i++) { - bits.append('0'); - } - } - String hex = new BigInteger(bits.toString(), 2).toString(16); - byte[] decodedHex = Hex.decodeHex(hex); - return new String(Base64.getUrlEncoder().encode(decodedHex)); - } - - public static int[] bitStringToIntArray(String bitString) { - if (bitString.isEmpty()) { - return new int[0]; - } - int sz = bitString.length(); - int[] result = new int[(sz + 7) / 8]; - - for (int i = 0; i < sz; i++) { - if (bitString.charAt(i) == '1') { - result[(i / 8)] |= 1 << (7 - (i % 8)); - } else { - result[(i / 8)] &= ~(1 << (7 - (i % 8))); - } - } - - return result; - } - - public static byte[] bitStringToByteArray(String bitString) { - if (bitString.isEmpty()) { - return new byte[0]; - } - int sz = bitString.length(); - byte[] result = new byte[(sz + 7) / 8]; - - for (int i = 0; i < sz; i++) { - if (bitString.charAt(i) == '1') { - result[(i / 8)] |= (byte) (1 << (7 - (i % 8))); - } else { - result[(i / 8)] &= (byte) ~(1 << (7 - (i % 8))); - } - } - - return result; - } - - public static byte[] concatBytes(byte[] a, byte[] b) { - byte[] c = new byte[a.length + b.length]; - System.arraycopy(a, 0, c, 0, a.length); - System.arraycopy(b, 0, c, a.length, b.length); - return c; - } - - public static int[] concatBytes(int[] a, int[] b) { - int[] c = new int[a.length + b.length]; - System.arraycopy(a, 0, c, 0, a.length); - System.arraycopy(b, 0, c, a.length, b.length); - return c; - } - - public static int[] append(int[] dst, int[] with) { - System.arraycopy(with, 0, dst, dst.length, with.length); - return dst; - } - - public static byte[] append(byte[] dst, byte[] with) { - System.arraycopy(with, 0, dst, dst.length, with.length); - return dst; - } - - public static byte[] appendByteArray(byte[] originalArray, byte[] appendArray) { - // Create a new array with size equal to the sum of both input arrays - byte[] resultArray = Arrays.copyOf(originalArray, originalArray.length + appendArray.length); - - // Copy the appendArray into the new array starting from the end of originalArray - System.arraycopy(appendArray, 0, resultArray, originalArray.length, appendArray.length); - - return resultArray; - } - - public static int[] copy(int[] dst, int destPos, int[] src, int srcPos) { - System.arraycopy(src, srcPos, dst, destPos, src.length); - return dst; - } - - public static byte[] copy(byte[] dst, int destPos, byte[] src, int srcPos) { - System.arraycopy(src, srcPos, dst, destPos, src.length); - return dst; - } - - public static byte[] slice(byte[] src, int from, int size) { - byte[] resultArray = new byte[size]; - System.arraycopy(src, from, resultArray, 0, size); - return resultArray; - } - - public static int dynInt(int[] data) { - int[] tmp = new int[8]; - Utils.copy(tmp, 8 - data.length, data, 0); - - return Integer.valueOf(Utils.bytesToHex(tmp), 16); - } - - public static int dynInt(byte[] data) { - byte[] tmp = new byte[8]; - Utils.copy(tmp, 8 - data.length, data, 0); - - return Integer.valueOf(Utils.bytesToHex(tmp), 16); - } - - public static byte[] dynamicIntBytes(BigInteger val, int sz) { - byte[] tmp = new byte[8]; - byte[] valArray = val.toByteArray(); // test just return val.toByteArray() - for (int i = 8 - valArray.length, j = 0; i < 8; i++, j++) { - tmp[i] = valArray[j]; - } - byte[] result = new byte[sz]; - System.arraycopy(tmp, 8 - sz, result, 0, sz); - return result; - } - - public static int log2Ceil(int val) { - return Integer.SIZE - Integer.numberOfLeadingZeros(val - 1); - } - - public static byte[] hexToSignedBytes(String hex) { - return hexStringToByteArray(hex); - } - - public static int[] hexToUnsignedBytes(String hex) { - return hexStringToIntArray(hex); - } - - public static int[] hexToInts(String hex) { - return hexStringToIntArray(hex); - } - - private static byte[] hexStringToByteArray(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = - (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); - } - return data; - } - - private static int[] hexStringToIntArray(String s) { - int[] result = new int[s.length() / 2]; - for (int i = 0; i < s.length(); i += 2) { - result[i / 2] = Integer.parseInt(s.substring(i, i + 2), 16); - } - return result; - } - - /** - * Signature algorithm, Implements ed25519. - * - * @return TweetNaclFast.Signature.KeyPair, where keyPair.getPublicKey() - 32 bytes and - * keyPair.getPrivateKey - 64 bytes - */ - public static TweetNaclFast.Signature.KeyPair generateSignatureKeyPair() { - return TweetNaclFast.Signature.keyPair(); - } - - /** - * Box algorithm, Public-key authenticated encryption - * - * @return TweetNaclFast.Box.KeyPair, where keyPair.getPublicKey() and keyPair.getPrivateKey. - */ - public static TweetNaclFast.Box.KeyPair generateKeyPair() { - return TweetNaclFast.Box.keyPair(); - } - - /** - * @param secretKey 32 bytes secret key - * @return TweetNaclFast.Signature.KeyPair, where keyPair.getPublicKey() - 32 bytes and - * keyPair.getPrivateKey - 64 bytes - */ - public static TweetNaclFast.Signature.KeyPair generateSignatureKeyPairFromSeed(byte[] secretKey) { - return TweetNaclFast.Signature.keyPair_fromSeed(secretKey); - } - - /** - * @param secretKey 32 bytes secret key - * @return TweetNaclFast.Box.KeyPair, where keyPair.getPublicKey() - 32 bytes and - * keyPair.getPrivateKey - 32 bytes - */ - public static TweetNaclFast.Box.KeyPair generateKeyPairFromSecretKey(byte[] secretKey) { - return TweetNaclFast.Box.keyPair_fromSecretKey(secretKey); - } - - /** - * If 32 bytes secret key is provided, then signature is generated out of it and its secret key is - * used. - * - * @param prvKey 32 or 64 bytes secret key. - */ - public static TweetNaclFast.Signature getSignature(byte[] pubKey, byte[] prvKey) { - TweetNaclFast.Signature signature; - if (prvKey.length == 64) { - signature = new TweetNaclFast.Signature(pubKey, prvKey); - } else { - TweetNaclFast.Signature.KeyPair keyPair = generateSignatureKeyPairFromSeed(prvKey); - signature = new TweetNaclFast.Signature(pubKey, keyPair.getSecretKey()); - } - return signature; - } - - /** - * Signs data - * - * @param pubKey 32 bytes pubKey - * @param prvKey 32 or 64 bytes prvKey - * @param data data to sign - * @return byte[] signature - */ - public static byte[] signData(byte[] pubKey, byte[] prvKey, byte[] data) { - TweetNaclFast.Signature signature; - if (prvKey.length == 64) { - signature = new TweetNaclFast.Signature(pubKey, prvKey); - } else { - TweetNaclFast.Signature.KeyPair keyPair = generateSignatureKeyPairFromSeed(prvKey); - signature = new TweetNaclFast.Signature(pubKey, keyPair.getSecretKey()); - } - return signature.detached(data); - } - - public static String toUTC(long timestamp) { - return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") - .format(LocalDateTime.ofEpochSecond(timestamp, 0, ZoneOffset.UTC)); - } - - public static OS getOS() { - - String operSys = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); - String operArch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); - - if (operSys.contains("win")) { - if ((operArch.contains("arm")) || (operArch.contains("aarch"))) { - return OS.WINDOWS_ARM; - } else { - return OS.WINDOWS; - } - } else if (operSys.contains("nix") || operSys.contains("nux") || operSys.contains("aix")) { - if ((operArch.contains("arm")) || (operArch.contains("aarch"))) { - return OS.LINUX_ARM; - } else { - return OS.LINUX; - } - } else if (operSys.contains("mac")) { - if ((operArch.contains("arm")) || (operArch.contains("aarch")) || (operArch.contains("m1"))) { - return OS.MAC_ARM64; - } else { - return OS.MAC; - } - } else { - return OS.UNKNOWN; - } - } - - public static String streamToString(InputStream is) { - try { - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - return br.lines().collect(Collectors.joining()); - } catch (Exception e) { - return null; - } - } - - public static BigInteger toNano(long toncoins) { - checkToncoinsOverflow(BigInteger.valueOf(toncoins).multiply(BI_BLN1)); - return BigInteger.valueOf(toncoins * BLN1); - } - - public static BigInteger toNano(String toncoins) { - checkToncoinsOverflow(new BigDecimal(toncoins).multiply(BD_BLN1).toBigInteger()); - - if (toncoins.matches("^\\d*\\.\\d+|\\d+\\.\\d*$")) { - return new BigDecimal(toncoins).multiply(BigDecimal.valueOf(BLN1)).toBigInteger(); - } else { - return new BigInteger(toncoins).multiply(BigInteger.valueOf(BLN1)); - } - } - - public static BigInteger toNano(double toncoins) { - checkToncoinsOverflow( - new BigDecimal(toncoins).multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); - if (BigDecimal.valueOf(toncoins).scale() > 9) { - throw new Error("Round the number to 9 decimals first"); - } - return BigDecimal.valueOf(toncoins * BLN1).toBigInteger(); - } - - public static BigInteger toNano(float toncoins) { - checkToncoinsOverflow( - new BigDecimal(toncoins).multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); - if (BigDecimal.valueOf(toncoins).scale() > 9) { - throw new Error("Round the number to 9 decimals first"); - } - return BigDecimal.valueOf(toncoins * BLN1).toBigInteger(); - } - - public static BigInteger toNano(BigDecimal toncoins) { - checkToncoinsOverflow(toncoins.multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); - if (toncoins.scale() > 9) { - throw new Error("Round the number to 9 decimals first"); - } - return toncoins.multiply(BigDecimal.valueOf(BLN1)).toBigInteger(); - } - - public static BigDecimal fromNano(BigInteger nanoCoins) { - checkToncoinsOverflow(nanoCoins); - return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP); - } - - public static BigDecimal fromNano(String nanoCoins) { - checkToncoinsOverflow(new BigInteger(nanoCoins)); - return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP); - } - - public static BigDecimal fromNano(long nanoCoins) { - checkToncoinsOverflow(BigInteger.valueOf(nanoCoins)); - return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP); - } - - public static BigDecimal fromNano(long nanoCoins, int scale) { - checkToncoinsOverflow(BigInteger.valueOf(nanoCoins)); - return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), scale, RoundingMode.HALF_UP); - } - - public static String formatCoins(BigDecimal toncoins) { - checkToncoinsOverflow(toncoins.multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); - if (toncoins.scale() > 9) { - throw new Error("Round the number to 9 decimals first"); - } - return String.format("%,.9f", toncoins.multiply(BigDecimal.valueOf(BLN1))); - } - - public static String formatCoins(String toncoins) { - BigInteger nano = toNano(toncoins); - return formatNanoValue(nano); - } - - public static String formatCoins(BigDecimal toncoins, int scale) { - BigInteger nano = toNano(toncoins); - return formatNanoValue(nano, scale); - } - - public static String formatCoins(String toncoins, int scale) { - BigInteger nano = toNano(toncoins); - return formatNanoValue(nano, scale); - } - - public static String formatNanoValue(String nanoCoins) { - checkToncoinsOverflow(new BigInteger(nanoCoins)); - return String.format( - "%,.9f", - new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); - } - - public static String formatNanoValue(long nanoCoins) { - checkToncoinsOverflow(BigInteger.valueOf(nanoCoins)); - return String.format( - "%,.9f", - new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); - } - - public static String formatNanoValue(BigInteger nanoCoins) { - checkToncoinsOverflow(nanoCoins); - return String.format( - "%,.9f", - new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); - } - - public static String formatNanoValueZero(BigInteger nanoCoins) { - checkToncoinsOverflow(nanoCoins); - if (nanoCoins.compareTo(BigInteger.ZERO) == 0) { - return "0"; - } else { - return String.format( - "%,.9f", - new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); - } - } - - public static String formatNanoValue(String nanoCoins, int scale) { - checkToncoinsOverflow(new BigInteger(nanoCoins)); - return String.format( - "%,." + scale + "f", - new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), scale, RoundingMode.HALF_UP)); - } - - public static String formatJettonValue(String jettons, int decimals, int scale) { - return String.format( - "%,." + scale + "f", - new BigDecimal(jettons).divide(BigDecimal.valueOf(Math.pow(10, decimals))), - scale, - RoundingMode.HALF_UP); - } - - public static String formatJettonValue(BigInteger jettons, int decimals, int scale) { - return String.format( - "%,." + scale + "f", - new BigDecimal(jettons).divide(BigDecimal.valueOf(Math.pow(10, decimals))), - scale, - RoundingMode.HALF_UP); - } - - public static String formatNanoValue(BigInteger nanoCoins, int scale) { - checkToncoinsOverflow(nanoCoins); - return String.format( - "%,." + scale + "f", - new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), scale, RoundingMode.HALF_UP)); - } - - public static void sleep(long seconds) { - try { - TimeUnit.SECONDS.sleep(seconds); - } catch (Throwable e) { - log.info(e.getMessage()); - } - } - - public static void sleep(long seconds, String text) { - try { - log.info(String.format("pause %s seconds, %s", seconds, text)); - TimeUnit.SECONDS.sleep(seconds); - } catch (Throwable e) { - log.info(e.getMessage()); - } - } - - public static int ip2int(String address) { - String[] parts = address.split(Pattern.quote(".")); - - if (parts.length != 4) { - throw new Error("Invalid IP address format."); - } - - int result = 0; - for (String part : parts) { - result = result << 8; - result |= Integer.parseInt(part); - } - return result; - } - - public static String int2ip(long ip) { - if ((ip < 0) && (ip + Math.pow(2, 32) > Math.pow(2, 31))) { - ip = (long) (ip + Math.pow(2, 32)); - } - return ((ip >> 24) & 0xFF) - + "." - + ((ip >> 16) & 0xFF) - + "." - + ((ip >> 8) & 0xFF) - + "." - + (ip & 0xFF); - } - - public static int[] reverseIntArray(int[] in) { - int i = 0, j = in.length - 1; - while (i < j) { - int tmp = in[i]; - in[i] = in[j]; - in[j] = tmp; - i++; - j--; - } - return in; - } - - public static byte[] reverseByteArray(byte[] in) { - int i = 0, j = in.length - 1; - while (i < j) { - byte tmp = in[i]; - in[i] = in[j]; - in[j] = tmp; - i++; - j--; - } - return in; - } - - public static long unsignedIntToLong(int x) { - // Integer.toUnsignedLong() - return x & 0x00000000ffffffffL; - } - - public static int unsignedShortToInt(short x) { - // Short.toUnsignedInt() - return x & 0x0000ffff; - } - - public static int unsignedByteToInt(byte x) { - return x & 0x00ff; - } - - private static void checkToncoinsOverflow(BigInteger amount) { - int bytesSize = (int) Math.ceil((amount.bitLength() / (double) 8)); - if (bytesSize >= 16) { - throw new Error("Value is too big. Maximum value 2^120-1"); - } - } - - public static String generateString(int length, String character) { - return RandomStringUtils.random(length, character); - } - - public static byte[] leftPadBytes(byte[] bits, int sz, char c) { - if (sz <= bits.length) { - return bits; - } - - int diff = sz - bits.length; - byte[] b = new byte[sz]; - Arrays.fill(b, 0, diff, (byte) c); - System.arraycopy(bits, 0, b, diff, bits.length); - - return b; - } - - public static byte[] rightPadBytes(byte[] bits, int sz, char c) { - if (sz <= bits.length) { - return bits; - } - - byte[] b = new byte[sz]; - System.arraycopy(bits, 0, b, 0, bits.length); - Arrays.fill(b, bits.length, sz, (byte) c); - - return b; - } - - public static int log2(int val) { - return (int) Math.ceil(Math.log(val) / Math.log(2)); - } - - public static int[] uintToBytes(int l) { - return new int[] {l}; - } - - public static byte[] byteToBytes(byte l) { - return new byte[] {l}; - } - - public static boolean compareBytes(byte[] a, byte[] b) { - return Arrays.equals(a, b); - } - - public static String detectAbsolutePath(String appName, boolean library) { - try { - if (library) { - appName = appName + "." + getLibraryExtension(); - } - ProcessBuilder pb; - if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == OS.WINDOWS_ARM)) { - pb = new ProcessBuilder("where", appName).redirectErrorStream(true); - } else { - pb = new ProcessBuilder("which", appName).redirectErrorStream(true); - } - Process p = pb.start(); - p.waitFor(1, TimeUnit.SECONDS); - String output = - new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8)) - .lines() - .collect(Collectors.joining("\n")); - String[] paths = output.split("\n"); - if (paths.length == 1) { - return paths[0]; - } else { - for (String path : paths) { - if (path.contains("ton")) { - return StringUtils.trim(path); - } + private static final Logger log = Logger.getLogger(Utils.class.getName()); + private static final String HEXES = "0123456789ABCDEF"; + private static final long BLN1 = 1000000000L; + private static final BigInteger BI_BLN1 = BigInteger.valueOf(BLN1); + private static final BigDecimal BD_BLN1 = BigDecimal.valueOf(BLN1); + + public enum OS { + WINDOWS, + WINDOWS_ARM, + LINUX, + LINUX_ARM, + MAC, + MAC_ARM64, + UNKNOWN + } + + /** + * uses POLY 0x1EDC6F41 + */ + public static Long getCRC32ChecksumAsLong(byte[] bytes) { + CRC32C crc32c = new CRC32C(); + crc32c.update(bytes, 0, bytes.length); + return crc32c.getValue() & 0x00000000ffffffffL; + } + + public static String getCRC32ChecksumAsHex(byte[] bytes) { + return BigInteger.valueOf(getCRC32ChecksumAsLong(bytes)).toString(16); + } + + public static byte[] getCRC32ChecksumAsBytes(byte[] bytes) { + return long4BytesToBytes(getCRC32ChecksumAsLong(bytes)); + } + + public static byte[] getCRC32ChecksumAsBytesReversed(byte[] bytes) { + byte[] b = long4BytesToBytes(getCRC32ChecksumAsLong(bytes)); + + byte[] reversed = new byte[4]; + reversed[0] = b[3]; + reversed[1] = b[2]; + reversed[2] = b[1]; + reversed[3] = b[0]; + + return reversed; + } + + /** + * Long to signed bytes + * + * @param l value + * @return array of unsigned bytes + */ + public static byte[] long4BytesToBytes(long l) { + byte[] result = new byte[4]; + for (int i = 3; i >= 0; i--) { + result[i] = (byte) (l & 0xFF); + l >>= 8; + } + return result; + } + + public static int[] longToBytes(long l) { + int[] result = new int[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (int) l & 0xFF; + l >>= 8; + } + return result; + } + + public static long bytesToLong(final byte[] b) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= (b[i] & 0xFF); + } + return result; + } + + public static int bytesToInt(final byte[] b) { + int result = 0; + for (int i = 0; i < 4; i++) { + result <<= 8; + result |= (b[i] & 0xFF); + } + return result; + } + + public static int bytesToIntX(final byte[] b) { + int result = 0; + for (byte value : b) { + result <<= 8; + result |= value & 0XFF; + } + return result; + } + + public static short bytesToShort(final byte[] b) { + short result = 0; + for (int i = 0; i < 2; i++) { + result <<= 8; + result |= (short) (b[i] & 0xFF); + } + return result; + } + + public static long intsToLong(final int[] b) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= b[i]; + } + return result; + } + + public static int intsToInt(final int[] b) { + int result = 0; + for (int i = 0; i < 4; i++) { + result <<= 8; + result |= b[i]; + } + return result; + } + + public static short intsToShort(final int[] b) { + short result = 0; + for (int i = 0; i < 2; i++) { + result <<= 8; + result |= (short) b[i]; + } + return result; + } + + public static int[] intToIntArray(int l) { + return new int[]{l}; + } + + public static byte[] intToByteArray(int value) { + return new byte[]{(byte) (value >>> 8), (byte) value}; + } + + // CRC-16/XMODEM + public static int getCRC16ChecksumAsInt(byte[] bytes) { + int crc = 0x0000; + int polynomial = 0x1021; + + for (byte b : bytes) { + for (int i = 0; i < 8; i++) { + boolean bit = ((b >> (7 - i) & 1) == 1); + boolean c15 = ((crc >> 15 & 1) == 1); + crc <<= 1; + if (c15 ^ bit) crc ^= polynomial; + } + } + + crc &= 0xffff; + return crc; + } + + public static int calculateMethodId(String methodName) { + int l = Utils.getCRC16ChecksumAsInt(methodName.getBytes()); + l = (l & 0xffff) | 0x10000; + return l; + } + + public static String getCRC16ChecksumAsHex(byte[] bytes) { + return bytesToHex(getCRC16ChecksumAsBytes(bytes)); + } + + public static byte[] getCRC16ChecksumAsBytes(byte[] bytes) { + return intToByteArray(getCRC16ChecksumAsInt(bytes)); + } + + public static String sha256(final String base) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + final byte[] hash = digest.digest(base.getBytes(StandardCharsets.UTF_8)); + final StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + final String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static String sha256(int[] bytes) { + byte[] converted = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + converted[i] = (byte) (bytes[i] & 0xff); + } + return sha256(converted); + } + + public static byte[] unsignedBytesToSigned(int[] bytes) { + byte[] converted = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + converted[i] = (byte) (bytes[i] & 0xff); + } + return converted; + } + + public static int[] signedBytesToUnsigned(byte[] bytes) { + int[] converted = new int[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + converted[i] = Byte.toUnsignedInt(bytes[i]); + } + return converted; + } + + public static byte[] sha256AsArray(byte[] bytes) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(bytes); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } + + public static byte[] sha1AsArray(byte[] bytes) { + try { + final MessageDigest digest = MessageDigest.getInstance("SHA-1"); + return digest.digest(bytes); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } + + public static byte[] md5AsArray(byte[] bytes) { + try { + final MessageDigest digest = MessageDigest.getInstance("MD5"); + return digest.digest(bytes); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } + + public static String md5(byte[] bytes) { + try { + final MessageDigest digest = MessageDigest.getInstance("MD5"); + final byte[] hash = digest.digest(bytes); + return Utils.bytesToHex(hash); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); } - } - return null; - } catch (Exception e) { - throw new Error( - "Cannot detect absolute path to executable " + appName + ", " + e.getMessage()); - } - } - - public static String getLibraryExtension() { - if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == OS.WINDOWS_ARM)) { - return "dll"; - } else if ((Utils.getOS() == OS.MAC) || (Utils.getOS() == OS.MAC_ARM64)) { - return "dylib"; - } else { - return "so"; - } - } - - public static int getRandomInt() { - return new Random().nextInt(); - } - - public static long getRandomLong() { - return new Random().nextLong(); - } - - public interface CStdLib extends Library { - int dup(int oldfd); // Duplicate a file descriptor - int dup2(int oldfd, int newfd); // Duplicate a file descriptor to a specified descriptor - int close(int fd); // Close a file descriptor - } - - // Redirect native output on Windows - static WinNT.HANDLE originalOut; - static WinNT.HANDLE originalErr; - static int originalStdoutFD; - static int originalStderrFD; - static CStdLib cStdLib; - - public static void disableNativeOutput() { -// System.out.println("disable"); - try { - if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { - // Redirect native output on Windows - originalOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE); - originalErr = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_ERROR_HANDLE); + } + public static String sha256(byte[] bytes) { try { - FileOutputStream nulStream = new FileOutputStream("NUL"); - - WinNT.HANDLE hNul = - Kernel32.INSTANCE.CreateFile( - "NUL", - Kernel32.GENERIC_WRITE, - Kernel32.FILE_SHARE_WRITE, - null, - Kernel32.OPEN_EXISTING, - 0, - null); - - // Redirect stdout and stderr to NUL - Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, hNul); - Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, hNul); - - // Close the handle to NUL - Kernel32.INSTANCE.CloseHandle(hNul); - } catch (IOException e) { - throw new RuntimeException(e); + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + final byte[] hash = digest.digest(bytes); + return Utils.bytesToHex(hash); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); } - } else if ((Utils.getOS() == Utils.OS.LINUX) || (Utils.getOS() == Utils.OS.LINUX_ARM)) { + } + + public static String sha1(byte[] bytes) { try { - // Load the native library - cStdLib = Native.load("c", CStdLib.class); - - // Save original stdout and stderr file descriptors - originalStdoutFD = cStdLib.dup(1); - originalStderrFD = cStdLib.dup(2); - - // Redirect stdout and stderr to /dev/null - try (FileOutputStream devNull = new FileOutputStream("/dev/null")) { - // Get the file descriptor for /dev/null - FileDescriptor fd = devNull.getFD(); - - // Get the file descriptor integer value by accessing the private field via reflection - // Retrieve the field that holds the actual fd (in a private field) - Field fdField = FileDescriptor.class.getDeclaredField("fd"); - fdField.setAccessible(true); - int devNullFD = (int) fdField.get(fd); - - cStdLib.dup2(devNullFD, 1); - cStdLib.dup2(devNullFD, 2); - - } catch (IOException | NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } + final MessageDigest digest = MessageDigest.getInstance("SHA-1"); + final byte[] hash = digest.digest(bytes); + return Utils.bytesToHex(hash); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + } + + public static String bitsToDec(boolean[] bits) { + StringBuilder s = new StringBuilder(); + for (boolean b : bits) { + s.append(b ? '1' : '0'); + } + return new BigInteger(s.toString(), 2).toString(10); + } + + public static String bitsToHex(boolean[] bits) { + StringBuilder s = new StringBuilder(); + for (boolean b : bits) { + s.append(b ? '1' : '0'); + } + return new BigInteger(s.toString(), 2).toString(16); + } + + public static String bytesToBitString(byte[] raw) { + String hex = Utils.bytesToHex(signedBytesToUnsigned(raw)); + BigInteger bi = new BigInteger(hex, 16); + return bi.toString(2); + } + + public static String bytesToBitString(int[] raw) { + String hex = Utils.bytesToHex(raw); + BigInteger bi = new BigInteger(hex, 16); + return bi.toString(2); + } + + public static String bytesToHex(byte[] raw) { + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final byte b : raw) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); + } + return hex.toString().toLowerCase(); + } + + public static String bytesToHex(int[] raw) { + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final int b : raw) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); + } + return hex.toString().toLowerCase(); + } + + public static String base64UrlSafeToHexString(String base64) { + byte[] decoded = Base64.getUrlDecoder().decode(base64); + return bytesToHex(decoded); + } + + public static String base64ToHexString(String base64) { + byte[] decoded = Base64.getDecoder().decode(base64); + return bytesToHex(decoded); + } + + public static String hexStringToBase64UrlSafe(String hex) throws DecoderException { + byte[] decodedHex = Hex.decodeHex(hex); + return new String(Base64.getUrlEncoder().encode(decodedHex)); + } + + public static String hexStringToBase64(String hex) throws DecoderException { + byte[] decodedHex = Hex.decodeHex(hex); + return new String(Base64.getEncoder().encode(decodedHex)); + } + + public static String base64ToBitString(String base64) { + byte[] decode = Base64.getDecoder().decode(base64); + return new BigInteger(1, decode).toString(2); + } + + public static String bytesToBase64(byte[] bytes) { + return Base64.getEncoder().encodeToString(bytes); + } + + public static String bytesToBase64(int[] bytes) { + return Base64.getEncoder().encodeToString(Utils.unsignedBytesToSigned(bytes)); + } + + public static String bytesToBase64SafeUrl(byte[] bytes) { + return Base64.getUrlEncoder().encodeToString(bytes); + } + + public static byte[] base64ToBytes(String base64) { + return Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); + } + + public static int[] base64ToUnsignedBytes(String base64) { + return Utils.signedBytesToUnsigned( + Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8))); + } + + public static byte[] base64ToSignedBytes(String base64) { + return Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); + } + + public static byte[] base64SafeUrlToBytes(String base64) { + return Base64.getUrlDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); + } + + public static String base64ToString(String base64) { + return new String(Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8))); + } + + public static String stringToBase64(String str) { + return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8)); + } + + public static String bitStringToHex(String binary) { + int toPad = (binary.length() % 8) == 0 ? 0 : 8 - (binary.length() % 8); + final StringBuilder bits = new StringBuilder(binary); + if (toPad != 0) { + for (int i = 0; i < toPad; i++) { + bits.append('0'); + } + } + return new BigInteger(bits.toString(), 2).toString(16); + } + + public static String bitStringToBase64(String binary) throws DecoderException { + int toPad = (binary.length() % 8) == 0 ? 0 : 8 - (binary.length() % 8); + final StringBuilder bits = new StringBuilder(binary); + if (toPad != 0) { + for (int i = 0; i < toPad; i++) { + bits.append('0'); + } + } + String hex = new BigInteger(bits.toString(), 2).toString(16); + byte[] decodedHex = Hex.decodeHex(hex); + return new String(Base64.getEncoder().encode(decodedHex)); + } + + public static String repeat(String str, int count) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) { + sb.append(str); + } + return sb.toString(); + } + + public static String bitStringToBase64UrlSafe(String binary) throws DecoderException { + int toPad = (binary.length() % 8) == 0 ? 0 : 8 - (binary.length() % 8); + final StringBuilder bits = new StringBuilder(binary); + if (toPad != 0) { + for (int i = 0; i < toPad; i++) { + bits.append('0'); + } + } + String hex = new BigInteger(bits.toString(), 2).toString(16); + byte[] decodedHex = Hex.decodeHex(hex); + return new String(Base64.getUrlEncoder().encode(decodedHex)); + } + + public static int[] bitStringToIntArray(String bitString) { + if (bitString.isEmpty()) { + return new int[0]; + } + int sz = bitString.length(); + int[] result = new int[(sz + 7) / 8]; + + for (int i = 0; i < sz; i++) { + if (bitString.charAt(i) == '1') { + result[(i / 8)] |= 1 << (7 - (i % 8)); + } else { + result[(i / 8)] &= ~(1 << (7 - (i % 8))); + } + } + + return result; + } + + public static byte[] bitStringToByteArray(String bitString) { + if (bitString.isEmpty()) { + return new byte[0]; + } + int sz = bitString.length(); + byte[] result = new byte[(sz + 7) / 8]; + + for (int i = 0; i < sz; i++) { + if (bitString.charAt(i) == '1') { + result[(i / 8)] |= (byte) (1 << (7 - (i % 8))); + } else { + result[(i / 8)] &= (byte) ~(1 << (7 - (i % 8))); + } + } + + return result; + } + + public static byte[] concatBytes(byte[] a, byte[] b) { + byte[] c = new byte[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + public static int[] concatBytes(int[] a, int[] b) { + int[] c = new int[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + public static int[] append(int[] dst, int[] with) { + System.arraycopy(with, 0, dst, dst.length, with.length); + return dst; + } + + public static byte[] append(byte[] dst, byte[] with) { + System.arraycopy(with, 0, dst, dst.length, with.length); + return dst; + } + + public static byte[] appendByteArray(byte[] originalArray, byte[] appendArray) { + // Create a new array with size equal to the sum of both input arrays + byte[] resultArray = Arrays.copyOf(originalArray, originalArray.length + appendArray.length); + + // Copy the appendArray into the new array starting from the end of originalArray + System.arraycopy(appendArray, 0, resultArray, originalArray.length, appendArray.length); + + return resultArray; + } + + public static int[] copy(int[] dst, int destPos, int[] src, int srcPos) { + System.arraycopy(src, srcPos, dst, destPos, src.length); + return dst; + } + + public static byte[] copy(byte[] dst, int destPos, byte[] src, int srcPos) { + System.arraycopy(src, srcPos, dst, destPos, src.length); + return dst; + } + + public static byte[] slice(byte[] src, int from, int size) { + byte[] resultArray = new byte[size]; + System.arraycopy(src, from, resultArray, 0, size); + return resultArray; + } + + public static int dynInt(int[] data) { + int[] tmp = new int[8]; + Utils.copy(tmp, 8 - data.length, data, 0); + + return Integer.valueOf(Utils.bytesToHex(tmp), 16); + } + + public static int dynInt(byte[] data) { + byte[] tmp = new byte[8]; + Utils.copy(tmp, 8 - data.length, data, 0); + + return Integer.valueOf(Utils.bytesToHex(tmp), 16); + } + + public static byte[] dynamicIntBytes(BigInteger val, int sz) { + byte[] tmp = new byte[8]; + byte[] valArray = val.toByteArray(); // test just return val.toByteArray() + for (int i = 8 - valArray.length, j = 0; i < 8; i++, j++) { + tmp[i] = valArray[j]; + } + byte[] result = new byte[sz]; + System.arraycopy(tmp, 8 - sz, result, 0, sz); + return result; + } + + public static int log2Ceil(int val) { + return Integer.SIZE - Integer.numberOfLeadingZeros(val - 1); + } + + public static byte[] hexToSignedBytes(String hex) { + return hexStringToByteArray(hex); + } + + public static int[] hexToUnsignedBytes(String hex) { + return hexStringToIntArray(hex); + } + + public static int[] hexToInts(String hex) { + return hexStringToIntArray(hex); + } + + private static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = + (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + private static int[] hexStringToIntArray(String s) { + int[] result = new int[s.length() / 2]; + for (int i = 0; i < s.length(); i += 2) { + result[i / 2] = Integer.parseInt(s.substring(i, i + 2), 16); + } + return result; + } + + /** + * Signature algorithm, Implements ed25519. + * + * @return TweetNaclFast.Signature.KeyPair, where keyPair.getPublicKey() - 32 bytes and + * keyPair.getPrivateKey - 64 bytes + */ + public static TweetNaclFast.Signature.KeyPair generateSignatureKeyPair() { + return TweetNaclFast.Signature.keyPair(); + } + + /** + * Box algorithm, Public-key authenticated encryption + * + * @return TweetNaclFast.Box.KeyPair, where keyPair.getPublicKey() and keyPair.getPrivateKey. + */ + public static TweetNaclFast.Box.KeyPair generateKeyPair() { + return TweetNaclFast.Box.keyPair(); + } + + /** + * @param secretKey 32 bytes secret key + * @return TweetNaclFast.Signature.KeyPair, where keyPair.getPublicKey() - 32 bytes and + * keyPair.getPrivateKey - 64 bytes + */ + public static TweetNaclFast.Signature.KeyPair generateSignatureKeyPairFromSeed(byte[] secretKey) { + return TweetNaclFast.Signature.keyPair_fromSeed(secretKey); + } + + /** + * @param secretKey 32 bytes secret key + * @return TweetNaclFast.Box.KeyPair, where keyPair.getPublicKey() - 32 bytes and + * keyPair.getPrivateKey - 32 bytes + */ + public static TweetNaclFast.Box.KeyPair generateKeyPairFromSecretKey(byte[] secretKey) { + return TweetNaclFast.Box.keyPair_fromSecretKey(secretKey); + } + + /** + * If 32 bytes secret key is provided, then signature is generated out of it and its secret key is + * used. + * + * @param prvKey 32 or 64 bytes secret key. + */ + public static TweetNaclFast.Signature getSignature(byte[] pubKey, byte[] prvKey) { + TweetNaclFast.Signature signature; + if (prvKey.length == 64) { + signature = new TweetNaclFast.Signature(pubKey, prvKey); + } else { + TweetNaclFast.Signature.KeyPair keyPair = generateSignatureKeyPairFromSeed(prvKey); + signature = new TweetNaclFast.Signature(pubKey, keyPair.getSecretKey()); + } + return signature; + } + + /** + * Signs data + * + * @param pubKey 32 bytes pubKey + * @param prvKey 32 or 64 bytes prvKey + * @param data data to sign + * @return byte[] signature + */ + public static byte[] signData(byte[] pubKey, byte[] prvKey, byte[] data) { + TweetNaclFast.Signature signature; + if (prvKey.length == 64) { + signature = new TweetNaclFast.Signature(pubKey, prvKey); + } else { + TweetNaclFast.Signature.KeyPair keyPair = generateSignatureKeyPairFromSeed(prvKey); + signature = new TweetNaclFast.Signature(pubKey, keyPair.getSecretKey()); + } + return signature.detached(data); + } + + public static String toUTC(long timestamp) { + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + .format(LocalDateTime.ofEpochSecond(timestamp, 0, ZoneOffset.UTC)); + } + + public static String toUTCTimeOnly(long timestamp) { + return DateTimeFormatter.ofPattern("HH:mm:ss") + .format(LocalDateTime.ofEpochSecond(timestamp, 0, ZoneOffset.UTC)); + } + + public static OS getOS() { + + String operSys = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + String operArch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); + + if (operSys.contains("win")) { + if ((operArch.contains("arm")) || (operArch.contains("aarch"))) { + return OS.WINDOWS_ARM; + } else { + return OS.WINDOWS; + } + } else if (operSys.contains("nix") || operSys.contains("nux") || operSys.contains("aix")) { + if ((operArch.contains("arm")) || (operArch.contains("aarch"))) { + return OS.LINUX_ARM; + } else { + return OS.LINUX; + } + } else if (operSys.contains("mac")) { + if ((operArch.contains("arm")) || (operArch.contains("aarch")) || (operArch.contains("m1"))) { + return OS.MAC_ARM64; + } else { + return OS.MAC; + } + } else { + return OS.UNKNOWN; + } + } + + public static String streamToString(InputStream is) { + try { + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + return br.lines().collect(Collectors.joining()); } catch (Exception e) { - System.out.println("error here " + e.getMessage()); + return null; } - } else if ((Utils.getOS() == Utils.OS.MAC) || (Utils.getOS() == Utils.OS.MAC_ARM64)) { - // Load the native library - CStdLib cStdLib = Native.load("c", CStdLib.class); - - // Redirect stdout and stderr to /dev/null - try (FileOutputStream devNull = new FileOutputStream("/dev/null")) { - // Get the file descriptor for /dev/null - FileDescriptor fd = devNull.getFD(); - - // Get the file descriptor integer value by accessing the private field via reflection - // Retrieve the field that holds the actual fd (in a private field) - Field fdField = FileDescriptor.class.getDeclaredField("fd"); - fdField.setAccessible(true); - int devNullFD = (int) fdField.get(fd); - - // Duplicate and redirect stdout and stderr - int stdoutFD = 1; // File descriptor for stdout - int stderrFD = 2; // File descriptor for stderr - cStdLib.dup2(devNullFD, stdoutFD); - cStdLib.dup2(devNullFD, stderrFD); - - } catch (IOException | NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); + } + + public static BigInteger toNano(long toncoins) { + checkToncoinsOverflow(BigInteger.valueOf(toncoins).multiply(BI_BLN1)); + return BigInteger.valueOf(toncoins * BLN1); + } + + public static BigInteger toNano(String toncoins) { + checkToncoinsOverflow(new BigDecimal(toncoins).multiply(BD_BLN1).toBigInteger()); + + if (toncoins.matches("^\\d*\\.\\d+|\\d+\\.\\d*$")) { + return new BigDecimal(toncoins).multiply(BigDecimal.valueOf(BLN1)).toBigInteger(); + } else { + return new BigInteger(toncoins).multiply(BigInteger.valueOf(BLN1)); + } + } + + public static BigInteger toNano(double toncoins) { + checkToncoinsOverflow( + new BigDecimal(toncoins).multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); + if (BigDecimal.valueOf(toncoins).scale() > 9) { + throw new Error("Round the number to 9 decimals first"); + } + return BigDecimal.valueOf(toncoins * BLN1).toBigInteger(); + } + + public static BigInteger toNano(float toncoins) { + checkToncoinsOverflow( + new BigDecimal(toncoins).multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); + if (BigDecimal.valueOf(toncoins).scale() > 9) { + throw new Error("Round the number to 9 decimals first"); + } + return BigDecimal.valueOf(toncoins * BLN1).toBigInteger(); + } + + public static BigInteger toNano(BigDecimal toncoins) { + checkToncoinsOverflow(toncoins.multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); + if (toncoins.scale() > 9) { + throw new Error("Round the number to 9 decimals first"); + } + return toncoins.multiply(BigDecimal.valueOf(BLN1)).toBigInteger(); + } + + public static BigDecimal fromNano(BigInteger nanoCoins) { + checkToncoinsOverflow(nanoCoins); + return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP); + } + + public static BigDecimal fromNano(String nanoCoins) { + checkToncoinsOverflow(new BigInteger(nanoCoins)); + return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP); + } + + public static BigDecimal fromNano(long nanoCoins) { + checkToncoinsOverflow(BigInteger.valueOf(nanoCoins)); + return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP); + } + + public static BigDecimal fromNano(long nanoCoins, int scale) { + checkToncoinsOverflow(BigInteger.valueOf(nanoCoins)); + return new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), scale, RoundingMode.HALF_UP); + } + + public static String formatCoins(BigDecimal toncoins) { + checkToncoinsOverflow(toncoins.multiply(BigDecimal.valueOf(BLN1)).toBigInteger()); + if (toncoins.scale() > 9) { + throw new Error("Round the number to 9 decimals first"); } - } - } - catch (Exception e) { - System.err.println("cannot disable native stdout"); - } - } - - public static void enableNativeOutput() { - try { - if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { - Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, originalOut); - Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, originalErr); - } else if ((Utils.getOS() == Utils.OS.LINUX) || (Utils.getOS() == Utils.OS.LINUX_ARM)) { - cStdLib.dup2(originalStdoutFD, 1); - cStdLib.dup2(originalStderrFD, 2); - } else if ((Utils.getOS() == Utils.OS.MAC) || (Utils.getOS() == Utils.OS.MAC_ARM64)) { - cStdLib.dup2(originalStdoutFD, 1); - cStdLib.dup2(originalStderrFD, 2); - } - } - catch (Exception e) { - System.err.println("cannot enable native stdout"); - } -// System.out.println("enable"); - } + return String.format("%,.9f", toncoins.multiply(BigDecimal.valueOf(BLN1))); + } + + public static String formatCoins(String toncoins) { + BigInteger nano = toNano(toncoins); + return formatNanoValue(nano); + } + + public static String formatCoins(BigDecimal toncoins, int scale) { + BigInteger nano = toNano(toncoins); + return formatNanoValue(nano, scale); + } + + public static String formatCoins(String toncoins, int scale) { + BigInteger nano = toNano(toncoins); + return formatNanoValue(nano, scale); + } + + public static String formatNanoValue(String nanoCoins) { + checkToncoinsOverflow(new BigInteger(nanoCoins)); + return String.format( + "%,.9f", + new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); + } + + public static String formatNanoValue(long nanoCoins) { + checkToncoinsOverflow(BigInteger.valueOf(nanoCoins)); + return String.format( + "%,.9f", + new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); + } + + public static String formatNanoValue(BigInteger nanoCoins) { + checkToncoinsOverflow(nanoCoins); + return String.format( + "%,.9f", + new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); + } + + public static String formatNanoValueZero(BigInteger nanoCoins) { + if (isNull(nanoCoins)) { + return "N/A"; + } + checkToncoinsOverflow(nanoCoins); + if (nanoCoins.compareTo(BigInteger.ZERO) == 0) { + return "0"; + } else { + return String.format( + "%,.9f", + new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), 9, RoundingMode.HALF_UP)); + } + } + + public static String formatNanoValue(String nanoCoins, int scale) { + checkToncoinsOverflow(new BigInteger(nanoCoins)); + return String.format( + "%,." + scale + "f", + new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), scale, RoundingMode.HALF_UP)); + } + + public static String formatJettonValue(String jettons, int decimals, int scale) { + return String.format( + "%,." + scale + "f", + new BigDecimal(jettons).divide(BigDecimal.valueOf(Math.pow(10, decimals))), + scale, + RoundingMode.HALF_UP); + } + + public static String formatJettonValue(BigInteger jettons, int decimals, int scale) { + return String.format( + "%,." + scale + "f", + new BigDecimal(jettons).divide(BigDecimal.valueOf(Math.pow(10, decimals))), + scale, + RoundingMode.HALF_UP); + } + + public static String formatNanoValue(BigInteger nanoCoins, int scale) { + checkToncoinsOverflow(nanoCoins); + return String.format( + "%,." + scale + "f", + new BigDecimal(nanoCoins).divide(BigDecimal.valueOf(BLN1), scale, RoundingMode.HALF_UP)); + } + + public static void sleep(long seconds) { + try { + TimeUnit.SECONDS.sleep(seconds); + } catch (Throwable e) { + log.info(e.getMessage()); + } + } + + public static void sleep(long seconds, String text) { + try { + log.info(String.format("pause %s seconds, %s", seconds, text)); + TimeUnit.SECONDS.sleep(seconds); + } catch (Throwable e) { + log.info(e.getMessage()); + } + } + + public static int ip2int(String address) { + String[] parts = address.split(Pattern.quote(".")); + + if (parts.length != 4) { + throw new Error("Invalid IP address format."); + } + + int result = 0; + for (String part : parts) { + result = result << 8; + result |= Integer.parseInt(part); + } + return result; + } + + public static String int2ip(long ip) { + if ((ip < 0) && (ip + Math.pow(2, 32) > Math.pow(2, 31))) { + ip = (long) (ip + Math.pow(2, 32)); + } + return ((ip >> 24) & 0xFF) + + "." + + ((ip >> 16) & 0xFF) + + "." + + ((ip >> 8) & 0xFF) + + "." + + (ip & 0xFF); + } + + public static int[] reverseIntArray(int[] in) { + int i = 0, j = in.length - 1; + while (i < j) { + int tmp = in[i]; + in[i] = in[j]; + in[j] = tmp; + i++; + j--; + } + return in; + } + + public static byte[] reverseByteArray(byte[] in) { + int i = 0, j = in.length - 1; + while (i < j) { + byte tmp = in[i]; + in[i] = in[j]; + in[j] = tmp; + i++; + j--; + } + return in; + } + + public static long unsignedIntToLong(int x) { + // Integer.toUnsignedLong() + return x & 0x00000000ffffffffL; + } + + public static int unsignedShortToInt(short x) { + // Short.toUnsignedInt() + return x & 0x0000ffff; + } + + public static int unsignedByteToInt(byte x) { + return x & 0x00ff; + } + + private static void checkToncoinsOverflow(BigInteger amount) { + int bytesSize = (int) Math.ceil((amount.bitLength() / (double) 8)); + if (bytesSize >= 16) { + throw new Error("Value is too big. Maximum value 2^120-1"); + } + } + + public static String generateString(int length, String character) { + return RandomStringUtils.random(length, character); + } + + public static byte[] leftPadBytes(byte[] bits, int sz, char c) { + if (sz <= bits.length) { + return bits; + } + + int diff = sz - bits.length; + byte[] b = new byte[sz]; + Arrays.fill(b, 0, diff, (byte) c); + System.arraycopy(bits, 0, b, diff, bits.length); + + return b; + } + + public static byte[] rightPadBytes(byte[] bits, int sz, char c) { + if (sz <= bits.length) { + return bits; + } + + byte[] b = new byte[sz]; + System.arraycopy(bits, 0, b, 0, bits.length); + Arrays.fill(b, bits.length, sz, (byte) c); + + return b; + } + + public static int log2(int val) { + return (int) Math.ceil(Math.log(val) / Math.log(2)); + } + + public static int[] uintToBytes(int l) { + return new int[]{l}; + } + + public static byte[] byteToBytes(byte l) { + return new byte[]{l}; + } + + public static boolean compareBytes(byte[] a, byte[] b) { + return Arrays.equals(a, b); + } + + public static String detectAbsolutePath(String appName, boolean library) { + try { + if (library) { + appName = appName + "." + getLibraryExtension(); + } + ProcessBuilder pb; + if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == OS.WINDOWS_ARM)) { + pb = new ProcessBuilder("where", appName).redirectErrorStream(true); + } else { + pb = new ProcessBuilder("which", appName).redirectErrorStream(true); + } + Process p = pb.start(); + p.waitFor(1, TimeUnit.SECONDS); + String output = + new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + String[] paths = output.split("\n"); + if (paths.length == 1) { + return paths[0]; + } else { + for (String path : paths) { + if (path.contains("ton")) { + return StringUtils.trim(path); + } + } + } + return null; + } catch (Exception e) { + throw new Error( + "Cannot detect absolute path to executable " + appName + ", " + e.getMessage()); + } + } + + public static String getLibraryExtension() { + if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == OS.WINDOWS_ARM)) { + return "dll"; + } else if ((Utils.getOS() == OS.MAC) || (Utils.getOS() == OS.MAC_ARM64)) { + return "dylib"; + } else { + return "so"; + } + } + + public static int getRandomInt() { + return new Random().nextInt(); + } + + public static long getRandomLong() { + return new Random().nextLong(); + } + + public interface CStdLib extends Library { + int dup(int oldfd); // Duplicate a file descriptor + + int dup2(int oldfd, int newfd); // Duplicate a file descriptor to a specified descriptor + + int close(int fd); // Close a file descriptor + } + + // Redirect native output on Windows + static WinNT.HANDLE originalOut; + static WinNT.HANDLE originalErr; + static int originalStdoutFD; + static int originalStderrFD; + static CStdLib cStdLib; + + public static void disableNativeOutput() { + // System.out.println("disable"); + try { + if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { + // Redirect native output on Windows + originalOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE); + originalErr = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_ERROR_HANDLE); + + try { + FileOutputStream nulStream = new FileOutputStream("NUL"); + + WinNT.HANDLE hNul = + Kernel32.INSTANCE.CreateFile( + "NUL", + Kernel32.GENERIC_WRITE, + Kernel32.FILE_SHARE_WRITE, + null, + Kernel32.OPEN_EXISTING, + 0, + null); + + // Redirect stdout and stderr to NUL + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, hNul); + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, hNul); + + // Close the handle to NUL + Kernel32.INSTANCE.CloseHandle(hNul); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else if ((Utils.getOS() == Utils.OS.LINUX) || (Utils.getOS() == Utils.OS.LINUX_ARM)) { + try { + // Load the native library + cStdLib = Native.load("c", CStdLib.class); + + // Save original stdout and stderr file descriptors + originalStdoutFD = cStdLib.dup(1); + originalStderrFD = cStdLib.dup(2); + + // Redirect stdout and stderr to /dev/null + try (FileOutputStream devNull = new FileOutputStream("/dev/null")) { + // Get the file descriptor for /dev/null + FileDescriptor fd = devNull.getFD(); + + // Get the file descriptor integer value by accessing the private field via reflection + // Retrieve the field that holds the actual fd (in a private field) + Field fdField = FileDescriptor.class.getDeclaredField("fd"); + fdField.setAccessible(true); + int devNullFD = (int) fdField.get(fd); + + cStdLib.dup2(devNullFD, 1); + cStdLib.dup2(devNullFD, 2); + + } catch (IOException | NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } catch (Exception e) { + System.out.println("error here " + e.getMessage()); + } + } else if ((Utils.getOS() == Utils.OS.MAC) || (Utils.getOS() == Utils.OS.MAC_ARM64)) { + // Load the native library + CStdLib cStdLib = Native.load("c", CStdLib.class); + + // Redirect stdout and stderr to /dev/null + try (FileOutputStream devNull = new FileOutputStream("/dev/null")) { + // Get the file descriptor for /dev/null + FileDescriptor fd = devNull.getFD(); + + // Get the file descriptor integer value by accessing the private field via reflection + // Retrieve the field that holds the actual fd (in a private field) + Field fdField = FileDescriptor.class.getDeclaredField("fd"); + fdField.setAccessible(true); + int devNullFD = (int) fdField.get(fd); + + // Duplicate and redirect stdout and stderr + int stdoutFD = 1; // File descriptor for stdout + int stderrFD = 2; // File descriptor for stderr + cStdLib.dup2(devNullFD, stdoutFD); + cStdLib.dup2(devNullFD, stderrFD); + + } catch (IOException | NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } catch (Exception e) { + System.err.println("cannot disable native stdout"); + } + } + + public static void enableNativeOutput() { + try { + if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, originalOut); + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, originalErr); + } else if ((Utils.getOS() == Utils.OS.LINUX) || (Utils.getOS() == Utils.OS.LINUX_ARM)) { + cStdLib.dup2(originalStdoutFD, 1); + cStdLib.dup2(originalStderrFD, 2); + } else if ((Utils.getOS() == Utils.OS.MAC) || (Utils.getOS() == Utils.OS.MAC_ARM64)) { + cStdLib.dup2(originalStdoutFD, 1); + cStdLib.dup2(originalStderrFD, 2); + } + } catch (Exception e) { + System.err.println("cannot enable native stdout"); + } + // System.out.println("enable"); + } + + public static String convertShardIdentToShard(BigInteger shardPrefix, int prefixBits) { + if (isNull(shardPrefix)) { + throw new Error("Shard prefix is null, should be in range 0..60"); + } + if (shardPrefix.compareTo(BigInteger.valueOf(60)) > 0) { + return shardPrefix.toString(16); + } + return BigInteger.valueOf(2) + .multiply(shardPrefix) + .add(BigInteger.ONE) + .shiftLeft(63 - prefixBits) + .toString(16); + } + + public static BigInteger longToUnsignedBigInteger(long num) { + BigInteger b = BigInteger.valueOf(num); + if (b.compareTo(BigInteger.ZERO) < 0) + b = b.add(BigInteger.ONE.shiftLeft(64)); + return b; + } + + public static BigInteger longToUnsignedBigInteger(String num) { + BigInteger b = new BigInteger(num); + if (b.compareTo(BigInteger.ZERO) < 0) + b = b.add(BigInteger.ONE.shiftLeft(64)); + return b; + } }