diff --git a/docs/sdk/MIGRATING_V1.md b/docs/sdk/MIGRATING_V1.md index db0c6f6f65..5e565b358a 100644 --- a/docs/sdk/MIGRATING_V1.md +++ b/docs/sdk/MIGRATING_V1.md @@ -1,544 +1,538 @@ -## Migrating from v1 -> v2 - -### Renamed `Ed25519PublicKey` -> `PublicKey` - * Added `boolean verify(byte[], byte[])` - * Verifies a message was signe by the respective private key. - * Added `boolean verifyTransaction(Transaction)` - * Verifies the transaction was signed by the respective private key. - * Removed `Key toKeyProto()` - * Removed `boolean hasPrefix()` - * Removed `SignatureCase getSignatureCase()` - -### Renamed `Ed25519PrivateKey` -> `PrivateKey` - * Added `byte[] signTransaction(Transaction)` - * Signs the `Transaction` and returns the signature. - * Added `PublicKey getPublicKey()` - * Added `PrivateKey fromLegacyMnemonic(byte[])` - * Renamed `boolean suppotsDeriviation()` -> `boolean isDerivable()` - * Removed `PrivateKey generate(SecurRandom)` - * Removed `PrivateKey fromKeystore(Keystore)` - * Use `Keystore.getEd25519()` instead. - * Removed `PrivateKey readKeystore(IntputStream, String)` - * Use `Keystore.fromStream()` instead. - * Removed `PrivateKey writeKeystore(OutStream, String)` - * Use `Keystore.export(OutputStream, String)` instead. - * Removed `Keystore toKeystore()` - * Use `new Keystore(PrivateKey)` followed by `Keystore.export(OutputStream, String)` instead. +# Migrating from v1 to v2 + +This guide outlines the key changes, renames, additions, and removals in the SDK migration from version 1 to version 2. It covers updates to classes, methods, and functionalities to help you transition your code effectively. + +## Key Renames and Structural Changes + +### Renamed `Ed25519PublicKey` to `PublicKey` +- **Added** `boolean verify(byte[], byte[])`: Verifies a message was signed by the respective private key. +- **Added** `boolean verifyTransaction(Transaction)`: Verifies the transaction was signed by the respective private key. +- **Removed** `Key toKeyProto()`. +- **Removed** `boolean hasPrefix()`. +- **Removed** `SignatureCase getSignatureCase()`. + +### Renamed `Ed25519PrivateKey` to `PrivateKey` +- **Added** `byte[] signTransaction(Transaction)`: Signs the `Transaction` and returns the signature. +- **Added** `PublicKey getPublicKey()`. +- **Added** `PrivateKey fromLegacyMnemonic(byte[])`. +- **Renamed** `boolean supportsDerivation()` to `boolean isDerivable()`. +- **Removed** `PrivateKey generate(SecureRandom)`. +- **Removed** `PrivateKey fromKeystore(Keystore)`: Use `Keystore.getEd25519()` instead. +- **Removed** `PrivateKey readKeystore(InputStream, String)`: Use `Keystore.fromStream()` instead. +- **Removed** `PrivateKey writeKeystore(OutputStream, String)`: Use `Keystore.export(OutputStream, String)` instead. +- **Removed** `Keystore toKeystore()`: Use `new Keystore(PrivateKey)` followed by `Keystore.export(OutputStream, String)` instead. ### Removed `ThresholdKey` - * Use `KeyList.withThreshold()` or `KeyList.setThreshold()` instead +- Use `KeyList.withThreshold()` or `KeyList.setThreshold()` instead. -### Renamed `PublicKey` -> `Key` - * Addeded by `PublicKey` - * Addeded by `PrivateKey` - * Addeded by `KeyList` - * Addeded by `ContractId` +### Renamed `PublicKey` to `Key` +- **Added** by `PublicKey`. +- **Added** by `PrivateKey`. +- **Added** by `KeyList`. +- **Added** by `ContractId`. ### `KeyList` - * Exposed `int threshold` - * Added `KeyList of(Key...)` - * Added `KeyList withThreshold(int)` - * Added `int getThreshold()` - * Removed `Key toProtoKey()` - * Removed `SignatureCase getSignatureCase()` - * Removed `byte[] toBytes()` +- **Exposed** `int threshold`. +- **Added** `KeyList of(Key...)`. +- **Added** `KeyList withThreshold(int)`. +- **Added** `int getThreshold()`. +- **Removed** `Key toProtoKey()`. +- **Removed** `SignatureCase getSignatureCase()`. +- **Removed** `byte[] toBytes()`. ### `Mnemonic` - * Exposed `boolean isLegacy` - * Added `Mnemonic.fromWords(List)` - * Added `Mnemonic generate12()` - * Added `Mnemoinc generate24()` - * Removed `Mnemonic generate()` - * Use `generate12()` or `generate24()` instead. - * Removed `byte[] toSeed()` - * Removed `Mnemonic(List)` - * Use `Mnemonic.fromWords(List)` instead. - -### Renamed `MnemonicValidationResult` -> `BadMnemonicException` - * Added `Mnemonic mnemonic` - * Added `BadMnemonicReason reason` - * Removed `boolean isOk()` - * Removed `String toString()` - * Removed `MnemonicValidationStatus status` - -### Renamed `MnemonicValidationStatus` -> `BadMnemonicReason` +- **Exposed** `boolean isLegacy`. +- **Added** `Mnemonic fromWords(List)`. +- **Added** `Mnemonic generate12()`. +- **Added** `Mnemonic generate24()`. +- **Removed** `Mnemonic generate()`: Use `generate12()` or `generate24()` instead. +- **Removed** `byte[] toSeed()`. +- **Removed** `Mnemonic(List)`: Use `Mnemonic.fromWords(List)` instead. + +### Renamed `MnemonicValidationResult` to `BadMnemonicException` +- **Added** `Mnemonic mnemonic`. +- **Added** `BadMnemonicReason reason`. +- **Removed** `boolean isOk()`. +- **Removed** `String toString()`. +- **Removed** `MnemonicValidationStatus status`. + +### Renamed `MnemonicValidationStatus` to `BadMnemonicReason` ### Removed `MirrorClient` - * Use `Client` instead, and set the mirror network using `setMirrorNetwork()` +- Use `Client` instead, and set the mirror network using `setMirrorNetwork()`. -### Renamed `MirrorSubscriptionHandle` -> `SubscriptionHandle` +### Renamed `MirrorSubscriptionHandle` to `SubscriptionHandle` -### Renamed `QueryBuilder` -> `Query` - * Changed `long getCost(Client)` -> `Hbar getCost(Client)` - * Removed `setPaymentTransaction()` - * Removed `setQueryPayment(long)` - * Removed `setMaxQueryPayment(long)` - * Removed `Query toProto()` +### Renamed `QueryBuilder` to `Query` +- **Changed** `long getCost(Client)` to `Hbar getCost(Client)`. +- **Removed** `setPaymentTransaction()`. +- **Removed** `setQueryPayment(long)`. +- **Removed** `setMaxQueryPayment(long)`. +- **Removed** `Query toProto()`. ### Combined `TransactionBuilder` and `Transaction` - * Added `Transaction fromBytes(byte[])` - * Added `byte[] toBytes()` - * Added `TransactionId getTransactionId()` - * Added `Hbar getMaxTransactionFee()` - * Added `String getTransactionMemo()` - * Added `Map getTransactionHashPerNode()` - * Added `Duration getTransactionValidDuration()` - * Added `Transaction signWithOpeator(Client)` - * Added `Transaction addSignature(PublicKey, byte[])` - * Added `Map> getSignatures()` - * Renamed `Transaction build(null)` -> `Transaction freeze()` - * Renamed `Transaction build(Client)` -> `Transaction freezeWith(Client)` - * Removed `setMaxQueryPayment(long)` - * Renamed `setNodeId(AccountId)` -> `setNodeAccountIds(List)` - -### `AccountBalanceQuery` extends [Query](#renamed-querybuilder-query) - * Added `AccountId getAccountId()` - * Added `ContractId getContractId()` - * Changed `Hbar execute(Client)` -> `AccountBalance execute(Client)` +- **Added** `Transaction fromBytes(byte[])`. +- **Added** `byte[] toBytes()`. +- **Added** `TransactionId getTransactionId()`. +- **Added** `Hbar getMaxTransactionFee()`. +- **Added** `String getTransactionMemo()`. +- **Added** `Map getTransactionHashPerNode()`. +- **Added** `Duration getTransactionValidDuration()`. +- **Added** `Transaction signWithOperator(Client)`. +- **Added** `Transaction addSignature(PublicKey, byte[])`. +- **Added** `Map> getSignatures()`. +- **Renamed** `Transaction build(null)` to `Transaction freeze()`. +- **Renamed** `Transaction build(Client)` to `Transaction freezeWith(Client)`. +- **Removed** `setMaxQueryPayment(long)`. +- **Renamed** `setNodeId(AccountId)` to `setNodeAccountIds(List)`. + +## Account-Related Changes + +### `AccountBalanceQuery` Extends `Query` +- **Added** `AccountId getAccountId()`. +- **Added** `ContractId getContractId()`. +- **Changed** `Hbar execute(Client)` to `AccountBalance execute(Client)`. ### Added `AccountBalance` - * Added `Hbar balance` - * Added `Map tokenBalances` - -### `AccountCreateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `Key getKey()` - * Added `Hbar getInitialBalance()` - * Added `boolean getReceiverSignatureRequired()` - * Added `AccountId getProxyAccountId()` - * Added `Duration getAutoRenewPeriod()` - * Removed `setSendRecordThreshold(long)` and `setSendRecordThreshold(Hbar)` - * Removed `setReceiveRecordThreshold(long)` and `setReceiveRecordThreshold(Hbar)` - -### `AccountDeleteTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `AccountId getAccountId()` - * Added `AccountId getTransferAccountId()` - * Renamed `setDeleteAccountId()` -> `setAccountId()` +- **Added** `Hbar balance`. +- **Added** `Map tokenBalances`. + +### `AccountCreateTransaction` Extends `Transaction` +- **Added** `Key getKey()`. +- **Added** `Hbar getInitialBalance()`. +- **Added** `boolean getReceiverSignatureRequired()`. +- **Added** `AccountId getProxyAccountId()`. +- **Added** `Duration getAutoRenewPeriod()`. +- **Removed** `setSendRecordThreshold(long)` and `setSendRecordThreshold(Hbar)`. +- **Removed** `setReceiveRecordThreshold(long)` and `setReceiveRecordThreshold(Hbar)`. + +### `AccountDeleteTransaction` Extends `Transaction` +- **Added** `AccountId getAccountId()`. +- **Added** `AccountId getTransferAccountId()`. +- **Renamed** `setDeleteAccountId()` to `setAccountId()`. ### `AccountId` - * Added `byte[] toBytes()` - * Added `AccountId fromBytes(byte[])` - * Renamed `long account` -> `long num` - * Removed `AccountId(AccountIDOrBuilder)` - * Removed `AccountId toProto()` +- **Added** `byte[] toBytes()`. +- **Added** `AccountId fromBytes(byte[])`. +- **Renamed** `long account` to `long num`. +- **Removed** `AccountId(AccountIDOrBuilder)`. +- **Removed** `AccountId toProto()`. ### `AccountInfo` - * Added `byte[] toBytes()` - * Added `AccountInfo fromBytes(byte[])` - * Added `List liveHashes` - * Changed `long balance` -> `Hbar balance` - * Renamed `generateSendRecordThreshold` -> `sendRecordThreshold` - * Renamed `generateReceiveRecordThreshold` -> `receiveRecordThreshold` - -### `AccountInfoQuery` extends [Query](#renamed-querybuilder-query) - * Added `AccountId getAccountId()` - -### `AccountRecordsQuery` extends [Query](#renamed-querybuilder-query) - * Added `AccountId getAccountId()` - -### `AccountStakersQuery` extends [Query](#renamed-querybuilder-query) - * Added `AccountId getAccountId()` - -### `AccountUpdateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `AccountId getAccountId()` - * Added `Key getKey()` - * Added `Hbar getInitialBalance()` - * Added `boolean getReceiverSignatureRequired()` - * Added `AccountId getProxyAccountId()` - * Added `Duration getAutoRenewPeriod()` - * Added `Instant getExpirationTime()` - * Removed `setSendRecordThreshold(long)` and `setSendRecordThreshold(Hbar)` - * Removed `setReceiveRecordThreshold(long)` and `setReceiveRecordThreshold(Hbar)` - -### Removed `CryptoTransferTranscation` - * Use `TransferTransaction` instead. - -### `TransferTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TransferTransaction addTokenTransfer(TokenId, AccountId, long)` - * Added `Map> getTokenTransfers()` - * Added `TransferTransaction addHbarTransfer(AccountId, Hbar)` - * Added `Map getHbarTransfers()` - -### Renamed `ContractBytecodeQuery` -> `ContractByteCodeQuery` extends [Query](#renamed-querybuilder-query) - * Added `ContractId getContractId()` - -### `ContractCallQuery` extends [Query](#renamed-querybuilder-query) - * Added `ContractId getContractId()` - * Added `long getGas()` - * Added `byte[] getFunctionParameters()` - -### `ContractCreateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `FileId getBytecodeFileId()` - * Added `Key getAdminKey()` - * Added `long getGas()` - * Added `Hbar getInitialBalance()` - * Added `Duration getAutoRenewDuration()` - * Added `AccountId getProxyAccountId()` - * Added `String getContractMemo()` - * Added `byte[] getConstructorParameters()` - * Removed `setInitialBalance(long)` - -### `ContractDeleteTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `ContractId getContractId()` - * Added `AccountId getTransferAccountId()` - * Added `ContractId getTransferContractId()` - -### `ContractExecuteTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `ContractId getContractId()` - * Added `long getGas()` - * Added `Hbar getPayableAmount()` - * Added `byte[] getFunctionParameters()` - * Removed `setPayableAmount(long)` +- **Added** `byte[] toBytes()`. +- **Added** `AccountInfo fromBytes(byte[])`. +- **Added** `List liveHashes`. +- **Changed** `long balance` to `Hbar balance`. +- **Renamed** `generateSendRecordThreshold` to `sendRecordThreshold`. +- **Renamed** `generateReceiveRecordThreshold` to `receiveRecordThreshold`. + +### `AccountInfoQuery` Extends `Query` +- **Added** `AccountId getAccountId()`. + +### `AccountRecordsQuery` Extends `Query` +- **Added** `AccountId getAccountId()`. + +### `AccountStakersQuery` Extends `Query` +- **Added** `AccountId getAccountId()`. + +### `AccountUpdateTransaction` Extends `Transaction` +- **Added** `AccountId getAccountId()`. +- **Added** `Key getKey()`. +- **Added** `Hbar getInitialBalance()`. +- **Added** `boolean getReceiverSignatureRequired()`. +- **Added** `AccountId getProxyAccountId()`. +- **Added** `Duration getAutoRenewPeriod()`. +- **Added** `Instant getExpirationTime()`. +- **Removed** `setSendRecordThreshold(long)` and `setSendRecordThreshold(Hbar)`. +- **Removed** `setReceiveRecordThreshold(long)` and `setReceiveRecordThreshold(Hbar)`. + +## Transfer and Transaction Changes + +### Removed `CryptoTransferTransaction` +- Use `TransferTransaction` instead. + +### `TransferTransaction` Extends `Transaction` +- **Added** `TransferTransaction addTokenTransfer(TokenId, AccountId, long)`. +- **Added** `Map> getTokenTransfers()`. +- **Added** `TransferTransaction addHbarTransfer(AccountId, Hbar)`. +- **Added** `Map getHbarTransfers()`. + +## Contract-Related Changes + +### Renamed `ContractBytecodeQuery` to `ContractByteCodeQuery` Extends `Query` +- **Added** `ContractId getContractId()`. + +### `ContractCallQuery` Extends `Query` +- **Added** `ContractId getContractId()`. +- **Added** `long getGas()`. +- **Added** `byte[] getFunctionParameters()`. + +### `ContractCreateTransaction` Extends `Transaction` +- **Added** `FileId getBytecodeFileId()`. +- **Added** `Key getAdminKey()`. +- **Added** `long getGas()`. +- **Added** `Hbar getInitialBalance()`. +- **Added** `Duration getAutoRenewDuration()`. +- **Added** `AccountId getProxyAccountId()`. +- **Added** `String getContractMemo()`. +- **Added** `byte[] getConstructorParameters()`. +- **Removed** `setInitialBalance(long)`. + +### `ContractDeleteTransaction` Extends `Transaction` +- **Added** `ContractId getContractId()`. +- **Added** `AccountId getTransferAccountId()`. +- **Added** `ContractId getTransferContractId()`. + +### `ContractExecuteTransaction` Extends `Transaction` +- **Added** `ContractId getContractId()`. +- **Added** `long getGas()`. +- **Added** `Hbar getPayableAmount()`. +- **Added** `byte[] getFunctionParameters()`. +- **Removed** `setPayableAmount(long)`. ### `ContractId` - * Added `byte[] toBytes()` - * Added `ContractId fromBytes(byte[])` - * Renamed `long contract` -> `long num` - * Removed `ContractId(ContractIDOrBuilder)` - * Removed `ContractId toProto()` - * Removed `SignatureCase getSignatureCase()` - * Removed `Key toProtoKey()` +- **Added** `byte[] toBytes()`. +- **Added** `ContractId fromBytes(byte[])`. +- **Renamed** `long contract` to `long num`. +- **Removed** `ContractId(ContractIDOrBuilder)`. +- **Removed** `ContractId toProto()`. +- **Removed** `SignatureCase getSignatureCase()`. +- **Removed** `Key toProtoKey()`. ### `ContractInfo` - * Added `byte[] toBytes()` - * Added `ContractInfo fromBytes(byte[])` +- **Added** `byte[] toBytes()`. +- **Added** `ContractInfo fromBytes(byte[])`. -### `ContractInfoQuery` extends [Query](#renamed-querybuilder-query) - * Added `ContractId getContractId()` - * Removed `Method getMethod()` +### `ContractInfoQuery` Extends `Query` +- **Added** `ContractId getContractId()`. +- **Removed** `Method getMethod()`. ### Removed `ContractRecordsQuery` -### `ContractUpdateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `ContractId getContractId()` - * Added `FileId getBytecodeFileId()` - * Added `Key getAdminKey()` - * Added `Duration getAutoRenewDuration()` - * Added `AccountId getProxyAccountId()` - * Added `String getContractMemo()` - * Added `Instant getExpirationTime()` +### `ContractUpdateTransaction` Extends `Transaction` +- **Added** `ContractId getContractId()`. +- **Added** `FileId getBytecodeFileId()`. +- **Added** `Key getAdminKey()`. +- **Added** `Duration getAutoRenewDuration()`. +- **Added** `AccountId getProxyAccountId()`. +- **Added** `String getContractMemo()`. +- **Added** `Instant getExpirationTime()`. + +## File-Related Changes ### `FileAppendTransaction` - * Added `FileId getFileId()` - * Added `byte[] getContents()` +- **Added** `FileId getFileId()`. +- **Added** `byte[] getContents()`. ### `FileContentsQuery` - * Added `FileId getFileId()` +- **Added** `FileId getFileId()`. ### `FileCreateTransaction` - * Added `byte[] getContents()` - * Added `Collection getKeys()` - * Added `Instant getExpirationTime()` - * Renamed `addKey(Key)` -> `setKeys(Key...)` +- **Added** `byte[] getContents()`. +- **Added** `Collection getKeys()`. +- **Added** `Instant getExpirationTime()`. +- **Renamed** `addKey(Key)` to `setKeys(Key...)`. ### `FileDeleteTransaction` - * Added `FileId getFileId()` +- **Added** `FileId getFileId()`. ### `FileId` - * Added `byte[] toBytes()` - * Added `FileId fromBytes(byte[])` - * Renamed `long file` -> `long num` - * Removed `FileId fromSolidityAddress()` - * Removed `FileId(FileIDOrBuilder)` - * Removed `FileId toProto()` - * Removed `String toSolidityAddress()` +- **Added** `byte[] toBytes()`. +- **Added** `FileId fromBytes(byte[])`. +- **Renamed** `long file` to `long num`. +- **Removed** `FileId fromSolidityAddress()`. +- **Removed** `FileId(FileIDOrBuilder)`. +- **Removed** `FileId toProto()`. +- **Removed** `String toSolidityAddress()`. ### `FileInfo` - * Added `byte[] toBytes()` - * Added `FileInfo fromBytes(byte[])` - * Update `List keys` -> `KeyList keys` +- **Added** `byte[] toBytes()`. +- **Added** `FileInfo fromBytes(byte[])`. +- **Updated** `List keys` to `KeyList keys`. ### `FileInfoQuery` - * Added `FileId getFileId()` +- **Added** `FileId getFileId()`. ### `FileUpdateTransaction` - * Added `FileId getFileId()` - * Added `byte[] getContents()` - * Added `Collection getKeys()` - * Added `Instant getExpirationTime()` - * Renamed `addKey(Key)` -> `setKeys(Key...)` +- **Added** `FileId getFileId()`. +- **Added** `byte[] getContents()`. +- **Added** `Collection getKeys()`. +- **Added** `Instant getExpirationTime()`. +- **Renamed** `addKey(Key)` to `setKeys(Key...)`. + +## Consensus and Topic Changes ### Removed `ConsensusClient` - * Use `Client` instead, and set mirror network using `Client.setMirrorNetwork()` - -### Removed `ConsensusToicMessage` - -### Renamed `MirrorConsensusTopicResponse` -> `TopicMessage` - * Added `TopicMessageChunk[] chunks` - * This will be non null for a topic message which is constructed from multiple transactions. - * Renamed `byte[] message` -> `byte[] contents` - * Removed `byte[] getMessage()` - * Removed `ConsensusTopicId topicId` - -### Renamed `MirrorConsensusTopicChunk` -> `TopicMessageChunk` - -### Renamed `MirrorTopicMessageQuery` -> `TopicMessageQuery` - * Added `setErrorHandler(BiConsumer)` - * This error handler will be called if the max retry count is exceeded, or - * if the subscribe callback errors out for a specific `TopicMessage` - * Changed `MirrorSubscriptionHandle subscribe(MirrorClient, Consumer, Consumer)` -> `subscribe(Client, Consumer)` - * Use `setErrorHandler()` instead of passing it in as the third parameter. - -### Renamed `ConsensusTopicCreateTransaction` -> `TopicCreateTransaction` - * Added `String getTopicMemo()` - * Added `Key getAdminKey()` - * Added `Key getSubmitKey()` - * Added `Duration getAutoRenewDuration()` - * Added `AccountId getAutoRenewAccountId()` - -### Renamed `ConsensusTopicDeleteTransaction` -> `TopicDeleteTransaction` - * Added `TopicId getTopicId()` - -### Renamed `ConsensusMessageSubmitTransaction` -> `TopicMessageSubmitTransaction` - * Added `TopicId getTopicId()` - * Added `byte[] getMessage()` - * Removed `setChunkInfo(TransactionId, int, int)` - -### Renamed `ConsensusTopicId` -> `TopicId` - * Renamed `long topic` -> `long num` - * Removed `ConsensusTopicId(TopicIDOrBuilder)` - -### Renamed `ConsensusTopicInfo` -> `TopicInfo` - * Added `byte[] toBytes()` - * Added `TopicInfo fromBytes()` - * Renamed `ConsensusTopicId id` -> `TopicId topicId` - -### Renamed `ConsensusTopicInfoQuery` -> `TopicInfoQuery` - * Added `TopicId getTopicId()` - -### Renamed `ConsensusTopicUpdateTransaction` -> `TopicUpdateTransaction` - * Added `TopicId getTopicId()` - * Added `String getTopicMemo()` - * Added `Key getAdminKey()` - * Added `Key getSubmitKey()` - * Added `Duration getAutoRenewDuration()` - * Added `AccountId getAutoRenewAccountId()` - -### `TokenAssociateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `AccountId getAccountId()` - * Added `List getTokenIds()` - * Renamed `addTokenId(TokenId)` -> `setTokenIds(List)` +- Use `Client` instead, and set mirror network using `Client.setMirrorNetwork()`. + +### Removed `ConsensusTopicMessage` + +### Renamed `MirrorConsensusTopicResponse` to `TopicMessage` +- **Added** `TopicMessageChunk[] chunks`: This will be non-null for a topic message constructed from multiple transactions. +- **Renamed** `byte[] message` to `byte[] contents`. +- **Removed** `byte[] getMessage()`. +- **Removed** `ConsensusTopicId topicId`. + +### Renamed `MirrorConsensusTopicChunk` to `TopicMessageChunk` + +### Renamed `MirrorTopicMessageQuery` to `TopicMessageQuery` +- **Added** `setErrorHandler(BiConsumer)`: This error handler will be called if the max retry count is exceeded, or if the subscribe callback errors out for a specific `TopicMessage`. +- **Changed** `MirrorSubscriptionHandle subscribe(MirrorClient, Consumer, Consumer)` to `subscribe(Client, Consumer)`: Use `setErrorHandler()` instead of passing it in as the third parameter. + +### Renamed `ConsensusTopicCreateTransaction` to `TopicCreateTransaction` +- **Added** `String getTopicMemo()`. +- **Added** `Key getAdminKey()`. +- **Added** `Key getSubmitKey()`. +- **Added** `Duration getAutoRenewDuration()`. +- **Added** `AccountId getAutoRenewAccountId()`. + +### Renamed `ConsensusTopicDeleteTransaction` to `TopicDeleteTransaction` +- **Added** `TopicId getTopicId()`. + +### Renamed `ConsensusMessageSubmitTransaction` to `TopicMessageSubmitTransaction` +- **Added** `TopicId getTopicId()`. +- **Added** `byte[] getMessage()`. +- **Removed** `setChunkInfo(TransactionId, int, int)`. + +### Renamed `ConsensusTopicId` to `TopicId` +- **Renamed** `long topic` to `long num`. +- **Removed** `ConsensusTopicId(TopicIDOrBuilder)`. + +### Renamed `ConsensusTopicInfo` to `TopicInfo` +- **Added** `byte[] toBytes()`. +- **Added** `TopicInfo fromBytes()`. +- **Renamed** `ConsensusTopicId id` to `TopicId topicId`. + +### Renamed `ConsensusTopicInfoQuery` to `TopicInfoQuery` +- **Added** `TopicId getTopicId()`. + +### Renamed `ConsensusTopicUpdateTransaction` to `TopicUpdateTransaction` +- **Added** `TopicId getTopicId()`. +- **Added** `String getTopicMemo()`. +- **Added** `Key getAdminKey()`. +- **Added** `Key getSubmitKey()`. +- **Added** `Duration getAutoRenewDuration()`. +- **Added** `AccountId getAutoRenewAccountId()`. + +## Token-Related Changes + +### `TokenAssociateTransaction` Extends `Transaction` +- **Added** `AccountId getAccountId()`. +- **Added** `List getTokenIds()`. +- **Renamed** `addTokenId(TokenId)` to `setTokenIds(List)`. ### Removed `TokenBalanceQuery` - * Use `AccountBalanceQuery` to fetch token balances since `AccountBalance` contains `tokenBalances`. - -### `TokenBurnTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `long getAmount()` - -### `TokenCreateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `AccountID getTreasuryAccountId()` - * Added `Key getAdminKey()` - * Added `Key getKycKey()` - * Added `Key getSupplyKey()` - * Added `Key getWipeKey()` - * Added `Key getFreezeKey()` - * Added `boolean getFreezeDefault()` - * Added `Instant getExpirationTime()` - * Added `AccountId getAutoRenewAccountId()` - * Added `Duration getAutoRenewPeriod()` - * Added `int getDecimals()` - * Renamed `setName(String)` ->`setTokenName(String)` - * Renamed `setSymbol(String)` ->`setTokenSymbol(String)` - * Renamed `setTreasury(AccountId)` ->`setTreasuryAccountId(AccountId)` - * Renamed `setAutoRenewAccount(AccountId)` ->`setAutoRenewAccountId(AccountId)` - -### `TokenDeleteTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - -### `TokenDisassociateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `AccountId getAccountId()` - * Added `List getTokenIds()` - * Renamed `addTokenId(TokenId)` -> `setTokenIds(List)` - -### `TokenFreezeTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `AccountId getAccointId()` - -### `TokenGrantKycTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `AccountId getAccointId()` +- Use `AccountBalanceQuery` to fetch token balances since `AccountBalance` contains `tokenBalances`. + +### `TokenBurnTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `long getAmount()`. + +### `TokenCreateTransaction` Extends `Transaction` +- **Added** `AccountId getTreasuryAccountId()`. +- **Added** `Key getAdminKey()`. +- **Added** `Key getKycKey()`. +- **Added** `Key getSupplyKey()`. +- **Added** `Key getWipeKey()`. +- **Added** `Key getFreezeKey()`. +- **Added** `boolean getFreezeDefault()`. +- **Added** `Instant getExpirationTime()`. +- **Added** `AccountId getAutoRenewAccountId()`. +- **Added** `Duration getAutoRenewPeriod()`. +- **Added** `int getDecimals()`. +- **Renamed** `setName(String)` to `setTokenName(String)`. +- **Renamed** `setSymbol(String)` to `setTokenSymbol(String)`. +- **Renamed** `setTreasury(AccountId)` to `setTreasuryAccountId(AccountId)`. +- **Renamed** `setAutoRenewAccount(AccountId)` to `setAutoRenewAccountId(AccountId)`. + +### `TokenDeleteTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. + +### `TokenDissociateTransaction` Extends `Transaction` +- **Added** `AccountId getAccountId()`. +- **Added** `List getTokenIds()`. +- **Renamed** `addTokenId(TokenId)` to `setTokenIds(List)`. + +### `TokenFreezeTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `AccountId getAccountId()`. + +### `TokenGrantKycTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `AccountId getAccountId()`. ### `TokenId` - * Added `byte[] toBytes()` - * Added `TokenId romBytes(byte[])` - * Removed `TokenId(TokenIDOrBuilder)` - * Removed `fromSolidityAddress(String)` - * Removed `String toSolidityAddress()` - * Removed `TokenId toProto()` +- **Added** `byte[] toBytes()`. +- **Added** `TokenId fromBytes(byte[])`. +- **Removed** `TokenId(TokenIDOrBuilder)`. +- **Removed** `fromSolidityAddress(String)`. +- **Removed** `String toSolidityAddress()`. +- **Removed** `TokenId toProto()`. ### `TokenInfo` - * Added `byte[] toBytes()` - * Added `TokenInfo romBytes(byte[])` - * Renamed `AccountId treasury` -> `AccountId treasuryAccountId` - * Renamed `Instant expiry` -> `Instant expirationTime` +- **Added** `byte[] toBytes()`. +- **Added** `TokenInfo fromBytes(byte[])`. +- **Renamed** `AccountId treasury` to `AccountId treasuryAccountId`. +- **Renamed** `Instant expiry` to `Instant expirationTime`. -### `TokenInfoQuery` extends [Query](#renamed-querybuilder-query) - * Added `TokenId getTokenId()` +### `TokenInfoQuery` Extends `Query` +- **Added** `TokenId getTokenId()`. -### `TokenMintTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `long getAmount()` +### `TokenMintTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `long getAmount()`. ### `TokenRelationship` - * Added `byte[] toBytes()` - * Added `TokenRelationship fromBytes(byte[])` +- **Added** `byte[] toBytes()`. +- **Added** `TokenRelationship fromBytes(byte[])`. -### `TokenRevokeKycTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `AccountId getAccointId()` +### `TokenRevokeKycTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `AccountId getAccountId()`. ### Removed `TokenTransferTransaction` - * Use `TransferTransaction` instead. - -### `TokenUnfreezeTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `AccountId getAccointId()` - -### `TokenUpdateTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `AccountID getTreasuryAccountId()` - * Added `Key getAdminKey()` - * Added `Key getKycKey()` - * Added `Key getSupplyKey()` - * Added `Key getWipeKey()` - * Added `Key getFreezeKey()` - * Added `boolean getFreezeDefault()` - * Added `Instant getExpirationTime()` - * Added `AccountId getAutoRenewAccountId()` - * Added `Duration getAutoRenewPeriod()` - * Added `int getDecimals()` - * Renamed `setName(String)` ->`setTokenName(String)` - * Renamed `setSymbol(String)` ->`setTokenSymbol(String)` - * Renamed `setTreasury(AccountId)` ->`setTreasuryAccountId(AccountId)` - * Renamed `setAutoRenewAccount(AccountId)` ->`setAutoRenewAccountId(AccountId)` - -### `TokenWipeTransaction` extends [Transaction](#combined-transactionbuilder-and-transaction) - * Added `TokenId getTokenId()` - * Added `AccountId getAccountId()` +- Use `TransferTransaction` instead. + +### `TokenUnfreezeTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `AccountId getAccountId()`. + +### `TokenUpdateTransaction` Extends `Transaction` +- **Added** `AccountId getTreasuryAccountId()`. +- **Added** `Key getAdminKey()`. +- **Added** `Key getKycKey()`. +- **Added** `Key getSupplyKey()`. +- **Added** `Key getWipeKey()`. +- **Added** `Key getFreezeKey()`. +- **Added** `boolean getFreezeDefault()`. +- **Added** `Instant getExpirationTime()`. +- **Added** `AccountId getAutoRenewAccountId()`. +- **Added** `Duration getAutoRenewPeriod()`. +- **Added** `int getDecimals()`. +- **Renamed** `setName(String)` to `setTokenName(String)`. +- **Renamed** `setSymbol(String)` to `setTokenSymbol(String)`. +- **Renamed** `setTreasury(AccountId)` to `setTreasuryAccountId(AccountId)`. +- **Renamed** `setAutoRenewAccount(AccountId)` to `setAutoRenewAccountId(AccountId)`. + +### `TokenWipeTransaction` Extends `Transaction` +- **Added** `TokenId getTokenId()`. +- **Added** `AccountId getAccountId()`. + +## System and Exception Changes ### `FreezeTransaction` - * Added `Instant getStartTime()` - * Added `Instant getEndTime()` +- **Added** `Instant getStartTime()`. +- **Added** `Instant getEndTime()`. ### Removed `HbarRangeException` - * If `Hbar` is out of range `Hedera` will error instead. +- If `Hbar` is out of range, `Hedera` will error instead. ### Removed `HederaConstants` - * No replacement. +- No replacement. ### Removed `HederaNetworkException` -### Renamed `HederaPrecheckStatusException` -> `PrecheckStatusException` +### Renamed `HederaPrecheckStatusException` to `PrecheckStatusException` -### Renamed `HederaReceiptStatusException` -> `ReceiptStatusException` +### Renamed `HederaReceiptStatusException` to `ReceiptStatusException` ### Removed `HederaRecordStatusException` - * `ReceiptStatusException` will be thrown instead. +- `ReceiptStatusException` will be thrown instead. -### Removed`HederaStatusException` - * A `PrecheckStatusException` or `ReceiptStatusException` will be thrown instead. +### Removed `HederaStatusException` +- A `PrecheckStatusException` or `ReceiptStatusException` will be thrown instead. ### Removed `HederaThrowable` - * No replacement. +- No replacement. ### Removed `LocalValidationException` - * No replacement. Local validation is no longer done. +- No replacement. Local validation is no longer done. ### `SystemDeleteTransaction` - * Added `FileId getFileId()` - * Added `ContractId getContractId()` - * Added `Instant getExpirationTime()` +- **Added** `FileId getFileId()`. +- **Added** `ContractId getContractId()`. +- **Added** `Instant getExpirationTime()`. ### `SystemUndeleteTransaction` - * Added `FileId getFileId()` - * Added `ContractId getContractId()` +- **Added** `FileId getFileId()`. +- **Added** `ContractId getContractId()`. ### `TransactionId` - * Added `byte[] toBytes()` - * Added `TransactionId fromBytes(byte[])` - * Removed `TransactionId(TransactionIDOrBuilder)` - * Removed `TransactionID toProto()` - * Removed `TransactionId withValidStart(AccountId, Instant)` - * Use `new TransactionId(AccountId, Instant)` instead. - * Removed `TransactionId(AccountId)` - * Use `TransactionId generate(AccountId)` instead. +- **Added** `byte[] toBytes()`. +- **Added** `TransactionId fromBytes(byte[])`. +- **Removed** `TransactionId(TransactionIDOrBuilder)`. +- **Removed** `TransactionID toProto()`. +- **Removed** `TransactionId withValidStart(AccountId, Instant)`: Use `new TransactionId(AccountId, Instant)` instead. +- **Removed** `TransactionId(AccountId)`: Use `TransactionId generate(AccountId)` instead. ### Removed `TransactionList` -### `TransactionReciept` - * Exposed `ExchangeRate exchangeRate` - * Exposed `AccountId accountId` - * Exposed `FileId fileId` - * Exposed `ContractId contractId` - * Exposed `TopicId topicId` - * Exposed `TokenId tokenId` - * Exposed `long topicSequenceNumber` - * Exposed `byte[] topicRunningHash` - * Added `byte[] toBytes()` - * Added `TransactionReceipt fromBytes()` - * Added `long totalSupply` - * Removed `AccountId getAccountId()` - * Use `AccountId accountId` directly instead. - * Removed `ContractId getContractId()` - * Use `ContractId contractId` directly instead. - * Removed `FileId getFileId()` - * Use `FileId fileId` directly instead. - * Removed `TokenId getTokenId()` - * Use `TokenId tokenId` directly instead. - * Removed `ConsensusTopicId getConsensusTopicId()` - * Use `TopicId topicId` directly instead. - * Removed `long getConsensusTopicSequenceNumber()` - * Use `long sequenceNumber` directly instead. - * Removed `byte[] getConsnsusTopicRunningHash()` - * Use `byte[] topicRunningHash` directly instead. - * Removed `TransactionReceipt toProto()` - -### `TransactionReceiptQuery` extends [Query](#renamed-querybuilder-query) - * Added `TransactionId getTransactionId()` +### `TransactionReceipt` +- **Exposed** `ExchangeRate exchangeRate`. +- **Exposed** `AccountId accountId`. +- **Exposed** `FileId fileId`. +- **Exposed** `ContractId contractId`. +- **Exposed** `TopicId topicId`. +- **Exposed** `TokenId tokenId`. +- **Exposed** `long topicSequenceNumber`. +- **Exposed** `byte[] topicRunningHash`. +- **Added** `byte[] toBytes()`. +- **Added** `TransactionReceipt fromBytes()`. +- **Added** `long totalSupply`. +- **Removed** `AccountId getAccountId()`: Use `AccountId accountId` directly instead. +- **Removed** `ContractId getContractId()`: Use `ContractId contractId` directly instead. +- **Removed** `FileId getFileId()`: Use `FileId fileId` directly instead. +- **Removed** `TokenId getTokenId()`: Use `TokenId tokenId` directly instead. +- **Removed** `ConsensusTopicId getConsensusTopicId()`: Use `TopicId topicId` directly instead. +- **Removed** `long getConsensusTopicSequenceNumber()`: Use `long sequenceNumber` directly instead. +- **Removed** `byte[] getConsensusTopicRunningHash()`: Use `byte[] topicRunningHash` directly instead. +- **Removed** `TransactionReceipt toProto()`. + +### `TransactionReceiptQuery` Extends `Query` +- **Added** `TransactionId getTransactionId()`. ### `TransactionRecord` - * Added `byte[] toBytes()` - * Added `TransactionRecord fromBytes()` - * Removed `ContractFunctionResult getContratcExecuteResult()` - * Use `ContractFunctionResult contractFunctionResult` directly instead. - * Removed `ContractFunctionResult getContratcCreateResult()` - * Use `ContractFunctionResult contractFunctionResult` directly instead. - * Removed `TransactionRecord toProto()` +- **Added** `byte[] toBytes()`. +- **Added** `TransactionRecord fromBytes()`. +- **Removed** `ContractFunctionResult getContractExecuteResult()`: Use `ContractFunctionResult contractFunctionResult` directly instead. +- **Removed** `ContractFunctionResult getContractCreateResult()`: Use `ContractFunctionResult contractFunctionResult` directly instead. +- **Removed** `TransactionRecord toProto()`. -### `TransactionRecordQuery` extends [Query](#renamed-querybuilder-query) - * Added `TransactionId getTransactionId()` +### `TransactionRecordQuery` Extends `Query` +- **Added** `TransactionId getTransactionId()`. + +## Hbar and Client Changes ### `Hbar` - * Added `Hbar fromString(CharSequence)` - * Added `Hbar fromString(CharSequence, HbarUnit)` - * Added `Hbar from(long, HbarUnit)` - * Added `Hbar from(BigDecimal, HbarUnit)` - * Added `BigDecimal getValue()` - * Added `String toString(HbarUnit)` - * Renamed `fromTinybar(long)` -> `fromTinybars(long)` - * Renamed `Hbar of(long)` -> `from(long)` - * Renamed `Hbar of(BigDecimal)` -> `from(BigDecimal)` - * Renamed `Hbar as(HbarUnit)` -> `to(HbarUnit)` - * Renamed `long asTinybar()` -> `long toTinybars()` +- **Added** `Hbar fromString(CharSequence)`. +- **Added** `Hbar fromString(CharSequence, HbarUnit)`. +- **Added** `Hbar from(long, HbarUnit)`. +- **Added** `Hbar from(BigDecimal, HbarUnit)`. +- **Added** `BigDecimal getValue()`. +- **Added** `String toString(HbarUnit)`. +- **Renamed** `fromTinybar(long)` to `fromTinybars(long)`. +- **Renamed** `Hbar of(long)` to `from(long)`. +- **Renamed** `Hbar of(BigDecimal)` to `from(BigDecimal)`. +- **Renamed** `Hbar as(HbarUnit)` to `to(HbarUnit)`. +- **Renamed** `long asTinybar()` to `long toTinybars()`. ### `Client` - * Added `void setMirrorNetwork(List)` - * Added `List getMirrorNetwork()` - * Added `Client forNetwork(Map)` - * Added `void ping(AccountId)` - * Added `PublicKey getOperatorPublicKey()` - * Added `Client setNetwork(Map)` - * Added `Map getNetwork()` - * Renamed `fromJson(String)` -> `fromConfig(String)` and `fromJson(Reader)` -> `fromConfig(Reader)` - * Renamed `fromFile(String)` -> `fromConfigFile(String)` and `fromFile(File)` -> `fromConfigFile(File)` - * Renamed `getOperatorId()` -> `getOperatorAccountId()` - * Removed `constructor(Map)` - * Removed `Client replaceNodes(Map)` - * Removed `Client setMaxTransactionFee(long)` - * Removed `Client setMaxQueryPayment(long)` - * Removed `AccountInfo getAccount(AccountId)` - * Removed `void getAccountAsync()` - * Removed `Hbar getAccountBalance(AccountId)` - * Use `AccountBalanceQuery` instead. - * Removed `void getAccountBalanceAsync()` - * Changed `Client setOperatorWith(AccountId, PublicKey, TransactionSigner)`-> `Client setOperatorWith(AccountId, PublicKey, Function)` - +- **Added** `void setMirrorNetwork(List)`. +- **Added** `List getMirrorNetwork()`. +- **Added** `Client forNetwork(Map)`. +- **Added** `void ping(AccountId)`. +- **Added** `PublicKey getOperatorPublicKey()`. +- **Added** `Client setNetwork(Map)`. +- **Added** `Map getNetwork()`. +- **Renamed** `fromJson(String)` to `fromConfig(String)` and `fromJson(Reader)` to `fromConfig(Reader)`. +- **Renamed** `fromFile(String)` to `fromConfigFile(String)` and `fromFile(File)` to `fromConfigFile(File)`. +- **Renamed** `getOperatorId()` to `getOperatorAccountId()`. +- **Removed** `constructor(Map)`. +- **Removed** `Client replaceNodes(Map)`. +- **Removed** `Client setMaxTransactionFee(long)`. +- **Removed** `Client setMaxQueryPayment(long)`. +- **Removed** `AccountInfo getAccount(AccountId)`. +- **Removed** `void getAccountAsync()`. +- **Removed** `Hbar getAccountBalance(AccountId)`: Use `AccountBalanceQuery` instead. +- **Removed** `void getAccountBalanceAsync()`. +- **Changed** `Client setOperatorWith(AccountId, PublicKey, TransactionSigner)` to `Client setOperatorWith(AccountId, PublicKey, Function)`. \ No newline at end of file diff --git a/docs/sdk/SDK_ANATOMY.md b/docs/sdk/SDK_ANATOMY.md index c2c01183da..ab0e00fde1 100644 --- a/docs/sdk/SDK_ANATOMY.md +++ b/docs/sdk/SDK_ANATOMY.md @@ -1,305 +1,613 @@ -> Please note that this document may be a bit outdated. - -## Tools and Libraries: - -Gradle plugins are used for including libraries and tools. - -### Protobufs: - -Files with extension `.proto` get compiled into classes used for serializing and deserializing data, which can be used to save data locally, or send data over a channel. - -### gRPC: - -Used to perform remote procedure calls. Tightly coupled to Protobufs. Protobufs define the remote procedure calls (rpcs within services), the gRPC layer implements the client and server code for connecting clients and servers via channels. Hedera extends the gRPC server code to perform the RPC, and the SDK extends the gRPC client code to call the RPC. - -### BouncyCastle: - -Encryption stuff. - -### Jupiter (Junit): - -Unit testing framework. - -### Jacoco: - -Reports code coverage (how much code actually gets tested by tests). - -### Error-prone: - -Augments the Java compiler to output more comprehensive errors and warnings. - -### JavaPoet: - -Library to assist in code generation (see **FunctionalExecutableProcessor**). - -## Classes: - -### `LockableList`: - -An internal utility class that represents a list of things, and which has these capabilities: -* It can be locked, which prevents the list from being mutated. -* It has an index which can be incremented with the `advance()` method, and the index will loop back around to 0 on reaching the end of the list. - -### `Client`: - -This is the wallet app's connection to the network. It has a `Network`, a `MirrorNetwork`, an `ExecutorService`, an `Operator`, and various fields for configuring behavior (for example `requestTimeout`, `maxTransactionFee`, `maxQueryPayment`). - -An `Operator` is an inner class of `Client`, and has an `AccountId`, a `PublicKey`, and a transaction signer (a function that does the signing of transactions). The `transactionSigner` defaults to `privateKey::sign`. - -A `Client` can be initialized from a config file (json). A `Client` can be initialized for previewnet, testnet, or mainnet, or a custom network, where a custom network is a list of `<"ipAddress:portNumber", AccountID>` pairs (in the form of a hashtable). Multiple endpoints may be mapped to the same node account ID (put another way, there may be multiple proxies for the same node). If initialized for previewnet, testnet, or mainnet, the `Client` just uses a hard-coded list of `<"ipAddress:portNumber", AccountID>` pairs. - -`executor` will be used to initialize the gRPC `BaseChannel`, and in the event that an RPC fails and needs to be retried after a delay, `executor` will be used to schedule that delayed retry. - -### `BaseNode`: - -Has an `address`, a `channel`, an `executor` (ultimately from `Client`), `lastUsed` and `useCount`. - -`BaseNode` is inherited by `Node` and `MirrorNode`. - -`BaseNode` keeps track of stats about how this node has been used, and it constructs its channel on demand, which is a `grpc.BaseChannel`, and which is built as a plaintext channel, using the `executor`, and using the user agent from `getUserAgent()`. - -`channel` is a `grpc.BaseChannel` instead of a normal `grpc.Channel` so that we can customize how it is set up (for example, we can give it the specified executor, and we can shut it down in the desired manner). - -`BaseNode` has the methods `inUse()`, which causes the `BaseNode` to record that it is being used, `getChannel()`, `close()`, and `getUserAgent()`. - -The user agent is a string that is used to identify the client to the server. In this case, it's `"hiero-sdk-java/v{NUMBER}"`. - -### `BaseNetwork`: - -This represents a network of `BaseNode`s. `Network` and `MirrorNetwork` inherit from this. - -Has these critical fields: -* `network`, which is a map of `>`. In `Network`, `KeyT` is `AccountId`, so that we can get a list of proxies for a given node account ID (each proxy is represented by a separate `BaseNodeT`). -* `nodes`, which is a list of all `ManageNodeT`s in the network. -* `healthyNodes`, a list of currently healthy nodes which are selected from while attempting to execute a transaction or query. -* `executor`, a reference to the executor which will be used to create channels for the `Node`s (in practice, this is always the `Client`'s executor). - -`setNetwork()` will update this `Network` to the given list. It will close a `Node` and remove it from this network if it is not in the given list, and then it will then add nodes from the list. - -### `Network`: - -This represents a network of Hedera nodes, a `Client` connects to a `Network`. - -`getNodeAccountIdsForExecute()` gets a list of N randomly selected `AccountId`s where N is 1/3rd (rounded up) of healthy nodes in this `Network`. This is used by `Query` and `Transaction` to populate their `nodeAccountId`s, lists containing the `AccountId`s of `Node`s that the `Query` or `Transaction` will be attempted with. - -### `Node`: - -This is a connection to one node in the network. Inherits from `BaseNode` (which is where much of the meat is). - -### `Executable`: - -An `Executable` object represents a request to the server. +# Hedera SDK Architecture Documentation + +> Last updated: August 2025 + +## Table of Contents +- [Tools and Libraries](#tools-and-libraries) +- [Core Architecture](#core-architecture) +- [Network Components](#network-components) +- [Execution Framework](#execution-framework) +- [Transaction System](#transaction-system) +- [Query System](#query-system) +- [Mirror Network](#mirror-network) +- [Build System](#build-system) +- [Best Practices](#best-practices) + +## Tools and Libraries + +### Core Dependencies + +#### Protobufs +Protocol Buffer files (`.proto`) are compiled into Java classes for serializing and deserializing data. These classes enable: +- Local data persistence +- Network communication +- Type-safe message handling + +#### gRPC +Remote procedure call framework tightly integrated with Protobufs: +- Defines RPCs within services in `.proto` files +- Generates client and server code for channel communication +- Hedera extends server code for RPC implementation +- SDK extends client code for RPC invocation + +#### BouncyCastle +Comprehensive cryptography library providing: +- Digital signature algorithms +- Hash functions +- Key generation and management +- Encryption/decryption utilities + +#### Testing Framework +- **JUnit Jupiter**: Modern unit testing framework +- **Jacoco**: Code coverage analysis and reporting +- **Mockito**: Mocking framework for unit tests + +#### Code Quality Tools +- **Error-prone**: Enhanced Java compiler with additional error detection +- **SpotBugs**: Static analysis for bug detection +- **Checkstyle**: Code style enforcement + +#### Code Generation +- **JavaPoet**: Library for programmatic Java code generation +- **FunctionalExecutableProcessor**: Custom annotation processor for method variants + +## Core Architecture + +### LockableList +Thread-safe utility class providing: +```java +public class LockableList { + // Prevents mutations when locked + public void lock() + + // Circular iteration through elements + public void advance() + + // Current element access + public T getCurrent() +} +``` -`Query` and `Transaction` both extend `Executable`, and then other classes in turn extend `Query` and `Transaction`. +**Use Cases:** +- Node rotation during retries +- Transaction ID cycling +- Failover mechanism implementation -The code for using any `Executable` subclass object should look something like this: +### Client +Central connection manager to the Hedera network: ```java -AccountBalance accountBalanceNew = new AccountBalanceQuery() - .setAccountId(newAccountId) - .execute(client); +public class Client { + private Network network; + private MirrorNetwork mirrorNetwork; + private ExecutorService executor; + private Operator operator; + + // Configuration fields + private Duration requestTimeout; + private Hbar maxTransactionFee; + private Hbar maxQueryPayment; +} ``` -`execute()` is a method of `Executable`, and how `execute()` actually behaves is determined by a handful of abstract methods that are overridden by `Executable`'s subclasses. - -`nodeAccountIds` is a `LockableList` field of `Executable`, and it is a list of nodes to which we will attempt to submit this transaction or query. `execute()` should attempt to submit the transaction or query to the current node in the list, and if the request fails, it should `advance()` the list and try again. +#### Operator (Inner Class) +```java +public static class Operator { + private AccountId accountId; + private PublicKey publicKey; + private Function transactionSigner; +} +``` -The methods that are meant to be overridden by the subclasses are: +#### Initialization Options +1. **Predefined Networks**: `Client.forTestnet()`, `Client.forMainnet()`, `Client.forPreviewnet()` +2. **Custom Network**: Map of `<"ipAddress:port", AccountId>` pairs +3. **Configuration File**: JSON-based client configuration -* `onExecute()` performs any pre-execution preparation. -* `onExecuteAsync()` performs the same function as `onExecute()`, and returns an initial future to be completed before `executeAsync()`'s future. -* `makeRequest()` generates the desired request proto message for the rpc. -* `mapResponse()` turns the response from the rpc into the desired return type. -* `mapResponseStatus()` turns the response from the rpc into a `Status`. -* `getMethodDescriptor()` returns a `grpc.MethodDescriptor`, which is an object that describes the rpc to be called. `MethodDescriptor`s are fetched from the grpc-generated `"*Service*.java"` classes. -* `getTransactionIdInternal()` is the unique ID for this transaction. +## Network Components -The `Executable` class implements `execute()` and `executeAsync()`, and then the `FunctionalExecutableProcessor` generates several variants of the `executeAsync()` method during the build process. The `@FunctionalExecutable` annotation triggers this code generation. This pattern also appears in `ChunkedTransaction`, `Transaction`, and `Query`. +### BaseNode +Foundation class for network nodes: -`Executable` has a public `executeAsync()` method that calls `onExecuteAsync()` and then chains onto that future a call to the private `executeAsync()` method, which sets up and makes the rpc with `grpc.ClientCalls.futureUnaryCall()`, and then chains to _that_ future a handler which handles the result of the rpc. Depending on the result of the rpc, the handler may complete the future that was returned by `executeAsync()`, _or_ it may chain onto that future _another_, recursive call to the internal `executeAsync()` method, and this is how `executeAsync()` loops through multiple attempts to execute with different nodes, up to `maxAttempts`. +```java +public abstract class BaseNode { + protected String address; + protected ManagedChannel channel; + protected ExecutorService executor; + protected Instant lastUsed; + protected long useCount; + + // Channel management + public ManagedChannel getChannel() + public void close() + + // Usage tracking + public void inUse() + public String getUserAgent() // Returns "hiero-sdk-java/v{VERSION}" +} +``` -Before the inner `executeAsync()` method will work, `nodeAccountIds`, which is a member of the `Executable` object, needs to be filled. This is implicitly done by the `onExecuteAsync()` method that is implemented by the subclasses. On each attempt we increment the index of `NodeAccountIds` looping through all of the elements. +### BaseNetwork +Generic network management: -It should also be noted that the future returned by `grpc.ClientCalls.futureUnaryCall()` is a guava `ListenableFuture`, and in order to return the right kind of future (`CompletableFuture`), `executeAsync()` uses some black magic from the `FutureConverter` class to convert the guava `ListenableFuture` into a `CompletableFuture`. +```java +public abstract class BaseNetwork { + // Node mapping: Key -> List of proxy nodes + protected Map> network; + + // All nodes in the network + protected List nodes; + + // Currently healthy nodes for load balancing + protected List healthyNodes; + + protected ExecutorService executor; +} +``` -`execute()` is simpler by virtue of not being async, but it does approximately the same thing, just without the future mumbo jumbo. +### Network (extends BaseNetwork) +Hedera consensus network implementation: -### `Query`: +```java +public class Network extends BaseNetwork { + // Returns 1/3 of healthy nodes (rounded up) for execution + public List getNodeAccountIdsForExecute() +} +``` -The `Query` class extends `Executable`. +### Node (extends BaseNode) +Individual Hedera consensus node connection with health monitoring and automatic failover capabilities. -It has a `builder`, a `headerBuilder`, a `paymentTransactionId`, a list of `paymentTransactions` (more on this in a moment), and then `queryPayment` and `maxQueryPayment` are Hbar amounts set by the user for the query fee. +### MirrorNetwork & MirrorNode +Specialized components for Hedera Mirror Node connectivity, providing historical data and real-time streaming capabilities. -A `Query` proto message is basically a union (`oneof`) of all the different kinds of query messages that can be sent. Every one of these query proto messages has a `QueryHeader` message nested in it. +## Execution Framework -The `Query` class implements most of the abstract methods from `Executable`, _except_ for `mapResponse()` and `getMethodDescriptor()`, which `Query` leaves to be overridden by its subclasses. +### Executable +Abstract base class for all network operations: -Query also adds some abstract methods of its own: -* `RequestT onMakeRequest(queryBuilder, queryHeader)`: because every type of `Query` proto message has a `QueryHeader` inside of it, this method has to place the `QueryHeader` inside of the internal `Query` message in addition to generally preparing the builder to build the request message. -* `ResponseHeader mapResponseHeader(Response)`: the same nested-header pattern is repeated here for the `Response` proto message. This method fetches the `ResponseHeader` message from the the particular query response message. This method seems to be used for `Query`'s implementation of `mapResponseStatus` to check the `precheckStatus`. It doesn't look like it's used for anything else. -* `QueryHeader mapRequestHeader(proto.Query)`: this actually fetches the header from _this_ request. I see it used for `toString()`, but nothing else. -* `void validateChecksums(client)`: checks whether checksums on entity IDs in the query are valid for the ledger that `client` is configured for (EG testnet or mainnet). +```java +public abstract class Executable { + protected LockableList nodeAccountIds; + protected int maxAttempts = 10; + + // Abstract methods for subclass implementation + protected abstract void onExecute(Client client); + protected abstract CompletableFuture onExecuteAsync(Client client); + protected abstract RequestT makeRequest(); + protected abstract O mapResponse(ResponseT response); + protected abstract Status mapResponseStatus(ResponseT response); + protected abstract MethodDescriptor getMethodDescriptor(); +} +``` -`Query` has an inner class, `QueryCostQuery` (a query for the cost of querying). This is basically a fake query to the network, not actually intended to be successful, that is made in order to get the cost from the response header. We then assume that the cost for our real query will be the same as the cost for our fake query. +#### Execution Flow +1. **Preparation**: `onExecuteAsync()` sets up node lists and parameters +2. **Request Creation**: `makeRequest()` generates protobuf message +3. **Network Call**: gRPC unary call with retry logic +4. **Response Processing**: `mapResponse()` converts to return type +5. **Error Handling**: Status checking and retry logic -`onExecute[async]()` seems to be where most of the action is in `Query`. It first makes sure that `nodeAccountIds` is filled, then it fetches the `queryPayment` amount (via `QueryCostQuery`) if one hasn't been set, then it generates the payment transactions for paying the query fee. The `paymentTransactions` list is a parallel array to `nodeAccountIds`. The `Query` proto message includes a `Transaction` proto message inside of it for paying the query fee, and `onExecuteAsync()` just goes ahead and builds a parallel array of `Transaction` messages which are to be used in the event that we attempt to send our query to that node. +#### Retry Mechanism +- Automatic node failover using `nodeAccountIds` rotation +- Exponential backoff for transient failures +- Maximum attempt limits with circuit breaker pattern -### `Transaction`: +### @FunctionalExecutable Annotation +Generates multiple method variants from a single async method: -The `Transaction` class extends `Executable`. +```java +// Original method +@FunctionalExecutable(type = TransactionReceipt.class) +public CompletableFuture executeAsync(Client client) { ... } + +// Generated variants: +// - void executeAsync(Client, BiConsumer) +// - void executeAsync(Client, Duration, BiConsumer) +// - TransactionReceipt execute(Client) +// - TransactionReceipt execute(Client, Duration) +``` -A transaction is used like this: -- Instantiate a subclass of `Transaction`. -- Call methods to configure it. -- OPTIONAL: -- Freeze the transaction. -- Add more signatures. -- Execute the transaction (it will be frozen if not already frozen, and will be signed with client operator). -- `execute()` returns (or in the case of `executeAsync()`, returns in future) a `TransactionResponse`. -- OPTIONAL: use the resulting `TransactionResponse` to get the `TransactionReceipt` for free, or pay a fee to get the `TransactionRecord`. Fetching either of these is itself a query. +## Transaction System -The `Transaction` class is greatly complicated by three factors: +### Transaction Architecture +Transactions follow a sophisticated multi-dimensional structure: -**A)** A `Transaction` object can correspond to one of three proto messages: -1. `TransactionList` -2. `Transaction` with `signedTransactionBytes` set -3. `Transaction` without `signedTransactionBytes` set (this form is deprecated) +``` +Transaction Matrix (Chunks × Nodes): + Node0 Node1 Node2 Node3 +Chunk0 T0 T1 T2 T3 +Chunk1 T4 T5 T6 T7 +Chunk2 T8 T9 T10 T11 +``` -**B)** `Transaction`'s relationship to `ChunkedTransaction`. +### Core Data Structures +```java +public abstract class Transaction> extends Executable { + // Transaction identification + protected LockableList transactionIds; + + // Parallel arrays for T×N matrix + protected List sigPairLists; // [T×N] + protected List innerSignedTransactions; // [T×N] + protected List outerTransactions; // [T×N] + + // Configuration + protected Hbar transactionFee; + protected Duration transactionValidDuration; + protected String memo; +} +``` -**C)** `Transaction`'s relationships to `ScheduleCreateTransaction` and `ScheduleInfo` +### Transaction Lifecycle -Before we delve in, let's discuss signatures. +#### 1. Construction Phase +```java +CryptoTransferTransaction transaction = new CryptoTransferTransaction() + .addHbarTransfer(fromAccount, Hbar.fromTinybars(-1000)) + .addHbarTransfer(toAccount, Hbar.fromTinybars(1000)) + .setTransactionMemo("Payment for services"); +``` -The `SignatureMap` proto message is defined in `BasicTypes.proto` and has a repeated `SignaturePair` field. A `SignaturePair` contains a public key and a signature, which combined can be used to confirm whether some data was signed by a party who holds a copy of the private key associated with that public key. +#### 2. Freezing Phase +```java +transaction.freezeWith(client); +// Transaction is now immutable and ready for signing +``` -Protobuf does not guarantee that all implementations of protobuf will serialize the same message the same way, it only guarantees that any implementation will be able to parse messages that were serialized by any other implementation. As such, for the purposes of signing or hashing a message, the message must be serialized into bytes before it can be signed or hashed, and the signature or hash verification must be performed on that same serialized bytes form of the message. +**Freezing Process:** +- Validates all required fields +- Generates `TransactionBody` protobuf messages +- Creates `innerSignedTransactions` matrix +- Locks transaction for modifications -#### Let's tackle factor A: +#### 3. Signing Phase +```java +// Additional signatures beyond operator +transaction.sign(privateKey1); +transaction.sign(privateKey2); +``` -We must first clarify that `TransactionList` is a proto message type that's only used internally by the SDK. It is _not_ used in any Hedera network. The `fromBytes()` and `toBytes()` methods in the SDK are not used for serializing or deserializing `Transaction`s into or from any proto messages that are sent to or from any Hedera network. +**Signature Management:** +- Automatic operator signing during execution +- Support for multi-signature transactions +- Signature aggregation in `SignatureMap` -The `fromBytes()` method tries to parse the input bytes to a `TransactionList` proto message, and to a `Transaction` proto message. The `protoc`-generated methods fail silently if the bytes do not encode the message type in question, so `fromBytes()` simply looks at the fields of the objects outputted by the parse methods to see if the bytes encoded either of those messages. +#### 4. Execution Phase +```java +TransactionResponse response = transaction.execute(client); +TransactionReceipt receipt = response.getReceipt(client); +TransactionRecord record = response.getRecord(client); // Requires fee +``` -The `fromBytes()` method then stores the results to an odd type: `Map>`, typically referred to as `txs` in the code ("transactions"). The `accountId` is the ID of the node that the transaction is addressed to. This type will begin to make sense as I address factor B. +### Protobuf Message Structure -Finally `fromBytes()` detects the type of `Transaction` with `dataCase()`, and it passes the `txs` to the constructor for the correct `Transaction` subclass. The subclass's constructor will always make use of the `Transaction(txs)` constructor, which will check to make sure the bodies of all of the transaction bodies in the `TransactionList` are identical using the `requireProtoMatches()` static method. +#### Current Format (Recommended) +```protobuf +message Transaction { + bytes signedTransactionBytes = 1; // Serialized SignedTransaction +} -Let's now discuss how the `Transaction` proto message is structured. In its deprecated form, `Transaction` would have two fields, `bodyBytes` and `sigMap`. `bodyBytes` contains the serialized bytes form of a `TransactionBody` proto message which contains the actual meat of the transaction. +message SignedTransaction { + bytes bodyBytes = 1; // Serialized TransactionBody + SignatureMap sigMap = 2; // All signatures +} +``` -In its _non_-deprecated form, the Transaction message just contains a `signedTransactionBytes` field, which is the bytes form of a `SignedTransaction` proto message (defined in `TransactionContents.proto`), which contains the `bodyBytes` and the `sigMap`. +#### Legacy Format (Deprecated) +```protobuf +message Transaction { + bytes bodyBytes = 2; // Direct TransactionBody bytes + SignatureMap sigMap = 3; // Signatures +} +``` -The `bodyBytes` are what need to be signed, but then on Hedera's end, they hash the `signedTransactionBytes` for some purpose. So the `TransactionBody` needs to be serialized before it can be signed, and then the `SignedTransaction` needs to be serialized before it can be hashed. +### ChunkedTransaction +Handles large data by splitting into multiple transactions: -`TransactionBody` contains a `transactionID`, a `nodeAccountID`, a `transactionFee` (which is the client's maximum tolerated fee), `transactionValidDuration` (the window of time for the network to process the transaction), a `memo`, and a `oneof` called `data` with the internal data for all the various transaction types. +```java +public abstract class ChunkedTransaction> extends Transaction { + @Override + protected void onFreeze(TransactionBody.Builder bodyBuilder) { + // Creates multiple transaction chunks + // Each chunk becomes a row in the T×N matrix + } +} +``` -The `toBytes()` method always serializes the contents of this `Transaction` as a `TransactionList`, and internally, we basically always think of a `Transaction` object as a `TransactionList`. +**Examples:** +- `FileAppendTransaction`: Large file uploads +- `ContractUpdateTransaction`: Large bytecode updates +- `TopicMessageSubmitTransaction`: Large messages -#### Now factor B, `Transaction`'s relationship to `ChunkedTransaction`: +### Scheduled Transactions +Deferred execution with multi-party signing: -I've mentioned that we internally think of a `Transaction` object as representing a `TransactionList`. There are two reasons for this: +```java +// Create base transaction +CryptoTransferTransaction transfer = new CryptoTransferTransaction() + .addHbarTransfer(account1, Hbar.fromTinybars(-1000)) + .addHbarTransfer(account2, Hbar.fromTinybars(1000)); + +// Convert to scheduled transaction +ScheduleCreateTransaction scheduled = transfer.schedule() + .setScheduleMemo("Multi-sig payment") + .setAdminKey(adminKey); + +TransactionResponse response = scheduled.execute(client); +ScheduleId scheduleId = response.getReceipt(client).scheduleId; + +// Additional parties can sign +new ScheduleSignTransaction() + .setScheduleId(scheduleId) + .execute(signerClient); +``` -1. As covered in `Executable`, we make multiple attempts, trying different nodes, cycling through `NodeAccountIds`, until we've reached `maxAttempts`. Similarly to `Query`, we need to create the `Transaction` proto messages for each `Node`, and so we keep around a `List` of `Transaction` proto messages which are basically the same transaction, but addressed to different nodes. -2. In the `ChunkedTransaction` subclass, we break one transaction into a series of transactions. For example, to append to a file,we have to break the data we want to append to the file into multiple chunks, because one `FileAppendTransaction` can only append a small amount of data. So in `ChunkedTransaction`, we need to keep a list of transactions that really _do_ represent _different transactions_, not just the same Transaction addressed to different nodes. +#### Key Methods +- `schedule()`: Converts transaction to `ScheduleCreateTransaction` +- `fromScheduledTransaction()`: Reconstructs transaction from `ScheduleInfo` +- `onScheduled()`: Abstract method for schedulable transaction body creation -So at the end of the day, it's best to think of a `Transaction` as containing something like a 2D array of `Transaction` proto messages. Here each row represents a transaction, and each column represents a node, and at the intersections I have put the indices of the associated `Transaction` and `SignedTransaction` proto messages in the `this.transactions` and `this.signedTransactions` lists: +## Query System +### Query Architecture +```java +public abstract class Query extends Executable { + protected Message.Builder builder; + protected QueryHeader.Builder headerBuilder; + + // Payment system + protected TransactionId paymentTransactionId; + protected List paymentTransactions; // Parallel to nodeAccountIds + protected Hbar queryPayment; + protected Hbar maxQueryPayment; +} ``` - N0 N1 N2 N3 -T0 0 1 2 3 -T1 4 5 6 7 -T2 8 9 10 11 -T3 12 13 14 15 -``` - -This is the best way to think about the members of the `Transaction` class. Even if your transaction will not be chunked, internally, the `Transaction` object will be set up like a chunked transaction with only one chunk, and the methods of `Transaction` are generally written to be compatible with the behavior of a chunked transaction. `ChunkedTransaction` overrides the `freezeWith()` method to create multiple rows in the 2D array. -There is a `transactionIds` field of type `LockableList`, which operates similarly to the `nodeAccountIds` lockable list in `Executable`. Together, the indices of `nodeAccountIds` and `transactionIds` specify the coordinate of an element in the 2D array. +### Query Payment System +All queries (except free queries) require payment: -Now we can discuss the various parallel arrays in a `Transaction` object and how they're related. `T` = transaction count, `N` = node count: +1. **Cost Estimation**: `QueryCostQuery` determines required payment +2. **Payment Generation**: Creates payment transactions for each target node +3. **Execution**: Includes payment transaction in query message -- `LockableList transactionIds[T]` -- `List sigPairLists[T*N]` -- `List innerSignedTransactions[T*N]` -- `List outerTransactions[T*N]` +### Query Lifecycle -A `TransactionId` is used to uniquely identify each transaction, so that when the same transaction is submitted to multiple nodes, only one transaction with the same ID will be permitted. It consists of the `AccountId` of the account who originated the transaction, and the timestamp. - -Remember that the name of the proto message that's ultimately sent is `Transaction`, and that `signedTransactionBytes` is a field in the `Transaction` proto message. So `innerSignedTransactions` gets populated first, and then `outerTransactions` gets populated with signed transactions. +#### 1. Configuration +```java +AccountBalanceQuery query = new AccountBalanceQuery() + .setAccountId(accountId) + .setMaxQueryPayment(Hbar.fromTinybars(1000)); +``` -Also be sure to keep in mind that a `SignatureMap` is itself a list of signature pairs, so each element of `sigPairLists` is a list of signature pairs. `SignatureMap`s permit for a transaction to be signed by multiple accounts. +#### 2. Execution with Automatic Payment +```java +AccountBalance balance = query.execute(client); +// SDK automatically: +// - Estimates cost via QueryCostQuery +// - Generates payment transactions +// - Includes payment in query message +``` -#### Factor C, Scheduled Transactions: +#### 3. Manual Payment Control +```java +Hbar cost = query.getCost(client); +query.setQueryPayment(cost); +AccountBalance balance = query.execute(client); +``` -Scheduled transactions would be more accurately described as _pending_ transactions. A `ScheduleCreateTransaction` proto message is sent to the network to indicate that you would like to open a scheduled transaction, and then the scheduled transaction will live on the network for up to a half hour. During that window, other accounts may sign the scheduled transaction with `ScheduleSignTransaction`, and they can refer to it by its `ScheduleId`. +### Abstract Methods +```java +// Query-specific message building +protected abstract RequestT onMakeRequest(Message.Builder queryBuilder, QueryHeader queryHeader); -On the proto side of things, the `ScheduleCreate` proto message contains a `SchedulableTransactionBody`, which in turn contains a `oneof` of all of the transaction bodies that are schedulable (not all transactions are schedulable). +// Response header extraction +protected abstract ResponseHeader mapResponseHeader(ResponseT response); -Because the schedulable transaction types are, well, already existing transaction types, the SDK user who wants to create a scheduled transaction first instantiates a normal `Transaction` subclass, and then derives a `ScheduleCreateTransaction` from that transaction, and then they execute that `ScheduleCreateTransaction` to actually create the scheduled transaction on the network. +// Request header extraction (for debugging) +protected abstract QueryHeader mapRequestHeader(Query.Query request); -The `Transaction` class has a couple of methods that interact with scheduled transactions: `schedule()` and `fromScheduledTransaction()`. +// Checksum validation +protected abstract void validateChecksums(Client client); +``` -`schedule()` is the method that turns this `Transaction` (whatever subclass it is) into a `ScheduleCreateTransaction`. It uses the `onScheduled()` abstract method that is implemented by the subclass to fill the `SchedulableTransactionBody` proto message in the new `ScheduleCreateTransaction` object before returning it. +### Common Query Types +- **Account Queries**: `AccountBalanceQuery`, `AccountInfoQuery` +- **Contract Queries**: `ContractCallQuery`, `ContractInfoQuery` +- **File Queries**: `FileContentsQuery`, `FileInfoQuery` +- **Network Queries**: `NetworkVersionInfoQuery`, `TransactionReceiptQuery` -`fromScheduledTransaction()` is a static constructor method that instantiates an appropriate `Transaction` subclass from a `SchedulableTransactionBody` proto message. It's used by `ScheduleInfo` (which is the response from `ScheduleInfoQuery`) to create an SDK representation of the scheduled transaction that was queried about. For example, if you query about a scheduled transaction, and it's a `CryptoTransfer` transaction, then when you call `scheduleInfo.getSceduledTransaction()`, it will use `Transaction.fromScheduledTransaction(transactionBody)` to create and return a new `CryptoTransferTransaction` object that contains all the info about the scheduled transaction. +## Mirror Network -#### Freezing: +### TopicMessageQuery +Real-time streaming from Hedera Mirror Nodes: -All methods that modify the transaction (including those of subclasses) are guarded with `requireNotFrozen()`. `isFrozen()` returns true if there is no `frozenBodyBuilder`. +```java +TopicMessageQuery query = new TopicMessageQuery() + .setTopicId(topicId) + .setStartTime(Instant.now()) + .subscribe(client, message -> { + System.out.println("Received: " + new String(message.contents)); + }); +``` -The `Transaction` is not immediately sent after freezing. Instead, the user of the SDK has an opportunity to add signatures. In `onExecuteAsync()`, The `Transaction` will be frozen if it is not already frozen, and it will be signed by the client's operator, but if any additional signatures are desired, they should be added after freezing and before executing. +#### Key Features +- **Streaming RPC**: Long-lived connection for real-time data +- **Automatic Chunking**: Reassembles large messages automatically +- **Error Handling**: Configurable retry and error handlers +- **Message Ordering**: Guaranteed in-order delivery per topic -`this.innerSignedTransactions` is populated by `freezeWith()`. `freezeWith()` creates only a single row of `innerSignedTransactions`. `ChunkedTransaction` overrides `freezeWith()` to create an actual 2D array of `SignedTransactions`. `this.outerTransactions`, however, is not populated until `makeRequest()` or some other, similar method that requires `Transaction` proto messages builds them using `buildTransaction()`. +#### Handler Configuration +```java +query.setCompletionHandler(() -> System.out.println("Stream completed")) + .setErrorHandler((error) -> System.err.println("Error: " + error)) + .setRetryHandler((error) -> true); // Return true to retry +``` -#### Abstract methods: +#### Message Chunking +Large topic messages are automatically chunked by consensus nodes and reassembled by the SDK: -`Transaction` overrides `getTransactionId()` to get the current transaction id. +```java +// SDK handles this automatically +message ConsensusTopicResponse { + ConsensusMessageChunkInfo chunkInfo = 5; + bytes message = 6; // Actual message content +} +``` -`Transaction` overrides `makeRequest()` to produce the Transaction request message. It also uses `buildTransactions()` to populate the `this.transactions` list. +### Mirror Node Capabilities +- **Transaction History**: Complete transaction records +- **Account History**: Historical account states +- **Smart Contract Events**: Event logs and state changes +- **Topic Messages**: Real-time consensus service data -`Transaction` overrides `mapResponse()` to create a `transactionResponse` and advance `transactionIds` to the next transaction. +## Build System -`Transaction` overrides `mapResponseStatus()`. +### FunctionalExecutableProcessor +Annotation processor that generates method variants: -`Transaction` adds the overridable abstract methods `onFreeze()`, `onScheduled()`, and `validateChecksums()` +#### Input +```java +@FunctionalExecutable(type = TransactionReceipt.class) +public CompletableFuture executeAsync(Client client) { + // Implementation +} +``` -`onFreeze()` takes a transaction body builder as an input, and should build out the body of the transaction. +#### Generated Interface (WithExecute.java) +```java +public interface WithExecute { + CompletableFuture executeAsync(Client client); + + // Generated variants + default void executeAsync(Client client, BiConsumer callback) { ... } + default void executeAsync(Client client, Duration timeout, BiConsumer callback) { ... } + default void executeAsync(Client client, Consumer onSuccess, Consumer onFailure) { ... } + default TransactionReceipt execute(Client client) { ... } + default TransactionReceipt execute(Client client, Duration timeout) { ... } +} +``` -`onScheduled()` does something similar with the schedulable body. +#### Implementation +```java +public class MyTransaction extends Transaction implements WithExecute { + @Override + public CompletableFuture executeAsync(Client client) { + // Your implementation here + } +} +``` -`validateChecksums()` has the same function as in `Query` +### Build Process Integration +1. **Compilation**: Standard Java compilation +2. **Annotation Processing**: `FunctionalExecutableProcessor` generates interfaces +3. **Code Generation**: JavaPoet creates method variants +4. **Final Compilation**: Generated code compiled with main sources -### `TopicMessageQuery` +## Best Practices -Unlike most classes in the Hedera SDK, this is _not_ a query to a Hedera Hashgraph network, it is a query to a _mirror_ network. As such, it is _not_ a subclass of `Query`, despite its name. +### Error Handling +```java +try { + TransactionResponse response = transaction.execute(client); + TransactionReceipt receipt = response.getReceipt(client); + + if (receipt.status == Status.SUCCESS) { + // Handle success + } +} catch (ReceiptStatusException e) { + // Handle known Hedera errors + System.err.println("Transaction failed: " + e.receipt.status); +} catch (PrecheckStatusException e) { + // Handle precheck failures + System.err.println("Precheck failed: " + e.status); +} catch (TimeoutException e) { + // Handle network timeouts + System.err.println("Request timed out"); +} +``` -To use a `TopicMessageQuery`, instantiate one, configure it to specify which messages you want to receive, add any custom handlers you want, and then call its `MakeStreamingCall()` method. The user must pass an `onNext()` handler to handle each response message. The mirror network of the given `Client` will be used. `MakeStreamingCall()` will make an asynchronous streaming rpc to one node in the mirror network. +### Resource Management +```java +// Always close clients to free resources +try (Client client = Client.forTestnet()) { + client.setOperator(accountId, privateKey); + + // Perform operations + +} // Client automatically closed +``` -The proto messages used under the hood are defined in `"proto/mirror/ConsensusService.proto"`, and response messages are parsed into `TopicMessage`s before being handed over to the `onNext()` handler. The `ConsensusTopicResponse` proto message contains a `chunkInfo` field of type `ConsensusMessageChunkInfo` , which is defined in `ConsensusSubmitMessage.proto`. `ConsensusTopicResponse` also has a `message` field, which is of type `bytes`, and these bytes are what the user is really querying for. The SDK does not do anything to parse these bytes. The meaning and parsing of these bytes is left to the user. +### Transaction Optimization +```java +// Batch related operations +Transaction transaction = new AccountCreateTransaction() + .setKey(publicKey) + .setInitialBalance(Hbar.fromTinybars(1000)) + .setMaxTransactionFee(Hbar.fromTinybars(200000)) // Set appropriate fees + .setTransactionValidDuration(Duration.ofSeconds(120)) // Reasonable duration + .freezeWith(client); + +// Add any additional signatures before execution +transaction.sign(additionalPrivateKey); + +TransactionResponse response = transaction.execute(client); +``` -The responses may be chunked. If they are, `TopicMessageQuery` will collect all of the chunks into one `TopicMessage` before passing it to the `onNext()` handler. The `initialTransactionID` field of each responses' `chunkInfo` field is used to identify which pending message this response is a chunk of and store it appropriately. `chunkInfo`'s `total` field is used to identify whether we've collected all of the chunks of a pending message, and if we have, we construct the `TopicMessage` and dispatch it to the `onNext()` handler. Because grpc works over HTTP, we're guaranteed to receive all of the chunks, and in the correct order (unless an error occurs, obviously), though chunks from different topic messages may be interleaved. +### Query Optimization +```java +// Set reasonable payment limits +AccountBalanceQuery query = new AccountBalanceQuery() + .setAccountId(accountId) + .setMaxQueryPayment(Hbar.fromTinybars(100000)); // Prevent unexpected costs -In addition to the `onNext()` handler, there are several optional handlers which can be set with `setCompletionHandler()`, `setErrorhandler()`, and `setRetryHandler()`. The retry handler returns a boolean to indicate whether the query should be retried. +AccountBalance balance = query.execute(client); +``` -### `FunctionalExecutable` and `FunctionalExecutableProcessor` +### Network Configuration +```java +// Configure appropriate timeouts and limits +Client client = Client.forTestnet() + .setOperator(accountId, privateKey) + .setRequestTimeout(Duration.ofSeconds(30)) + .setMaxTransactionFee(Hbar.fromTinybars(200000)) + .setMaxQueryPayment(Hbar.fromTinybars(100000)); +``` -These classes aren't themselves components of the SDK, they are components in the SDK's build process. `FunctionalExecutable` is a custom annotation defined in the `executable-annotation` directory, and we use this annotation is in the SDK source code to mark methods that require additional processing during the build process. This additional processing is performed by the `FunctionalExecutableProcessor`, which is defined in the `executable-processor` directory. +## Additional Components -The `FunctionalExecutableProcessor` uses the JavaPoet library to generate variations of each method marked with the `@FunctionalExecutable` annotation. It presumes that the marked method is named `"*Async"`, that it has a `Client` parameter (or you may specify `onClient=true` if the method is _on_ `Client`, and you may specify an additional input type with `inputType=T`), and that it returns a `CompletableFuture` (you may specify a type other than `O`, as shown in the example below), and the `FunctionalExecutableProcessor` adds to the class several variations of that method which build on that original `*Async()` method. +### Status and Error Codes +The SDK provides comprehensive status handling through the `Status` enum, mapping to Hedera's `ResponseCodeEnum`: -For example, if in class `Bar` you create the method `CompletableFuture fooAsync(Client client)` and mark it with `@FunctionalExecutable(type=Integer.class)` (`type` here is the return type of the marked method), the `FunctionalExecutableProcessor` will add the following methods to `Bar`: +```java +public enum Status { + OK, + INVALID_TRANSACTION, + PAYER_ACCOUNT_NOT_FOUND, + INVALID_NODE_ACCOUNT, + // ... hundreds of status codes +} +``` -- `void fooAsync(Client client, BiConsumer callback)` -- `void fooAsync(Client client, Duration timeout, BiConsumer callback)` -- `void fooAsync(Client client, Consumer onSuccess, Consumer onFailure)` -- `void fooAsync(Client client, Duration timeout, Consumer onSuccess, Consumer onFailure)` -- `Integer foo(Client client)` -- `Integer foo(Client client, Duration timeout)` +### Checksum Validation +Entity IDs support checksum validation to prevent accidental cross-network operations: -Note that the last two are synchronous versions, and in `Executable`, the synchronous variants generated from `executeAsync()` are overridden with manually programmed `execute()` methods. +```java +// Testnet account with checksum +AccountId accountId = AccountId.fromString("0.0.123-vfmkw"); // Testnet checksum +accountId.validateChecksum(client); // Validates against client's network +``` -The `FunctionalAnnotationProcessor` can't add these methods to `Bar` directly. Instead, it will generate a new interface called `WithFoo`. The `WithFoo` interface will have an abstract `CompletableFuture fooAsync(Client client)` method, and it will have all of the variations of the `fooAsync()` and `foo()` methods which are listed above, which will use and build on the abstract `fooAsync()` method. You are expected to declare `Bar` as an implementation of this generated `WithFoo` interface. You should use the `@Override` annotation on your `fooAsync()` method in `Bar` to make sure that it overrides the abstract `fooAsync()` method from `WithFoo`. +### Custom Transaction Types +The SDK supports all Hedera services: +- **Cryptocurrency Service**: Account and token operations +- **Smart Contract Service**: Contract deployment and execution +- **File Service**: File storage and retrieval +- **Consensus Service**: Topic creation and messaging +- **Token Service**: HTS token management +- **Schedule Service**: Scheduled transaction management +- **Network Service**: Network information and utilities -If you want to get a better grasp on what the `FunctionalExecutableProcessor` actually does, I suggest that after building the SDK, you should look at the files in `sdk/build/generated/sources/annotationProcessor/java/main/org/hiero/sdk/`. These are the `With*.java` files that are generated by the `FunctionalExecutableProcessor` during the build process. For example, the `WithExecute.java` file was generated because of the `@FunctionalExecutable` annotation on the `Executable.executeAsync()` method, and if you look at `WithExecute.java` side-by-side with the `FunctionalExecutableProcessor.process()` method body, you should be able to see how each of the default methods in the `WithExecute` interface were generated by the processor +--- -**This document is not comprehensive. There are classes I have not yet documented, or which I have only documented in passing, like `ChunkedTransaction`.** +*This documentation covers the core architecture and components of the Hedera Java SDK. For specific API usage examples and detailed method documentation, please refer to the JavaDoc and official SDK examples.* \ No newline at end of file diff --git a/docs/sdk/developer-guide.md b/docs/sdk/developer-guide.md index 6ffa36a98f..08ed6e9ae2 100644 --- a/docs/sdk/developer-guide.md +++ b/docs/sdk/developer-guide.md @@ -1,54 +1,56 @@ -## JVM +# Java SDK Setup and Maintenance -JDK 17 is required. The Temurin builds of [Eclipse Adoptium](https://adoptium.net/) are strongly recommended. +This guide provides instructions for setting up, building, testing, and maintaining the Java SDK project. It covers JVM requirements, build processes, testing, dependency management, and file updates to ensure a smooth development experience. -## Setup +## JVM Requirements -> Note that the below `./gradlew` commands should be run from the root of the project. +JDK 17 is required. The Temurin builds from Eclipse Adoptium are strongly recommended. -This project uses the -[Hiero Gradle Conventions](https://github.com/hiero-ledger/hiero-gradle-conventions) -Gradle setup. More details on how to work with the project can be found in the -[documentation](https://github.com/hiero-ledger/hiero-gradle-conventions#build). +## Setup Instructions -### Building +Note that all `./gradlew` commands should be run from the root of the project. + +This project uses the Hiero Gradle Conventions for its Gradle setup. For more details on working with the project, refer to the documentation. + +### Building the Project + +To build the project, run: ```sh ./gradlew assemble ``` -### Unit Tests +### Running Unit Tests + +To execute unit tests for the SDK, run: ```sh ./gradlew :sdk:test ``` -### Integration Tests +### Running Integration Tests -> The tests are only executed if the configuration is provided. -> That's why we need to pass the configuration file at the beginning of the command. +Integration tests are only executed if the required configuration is provided. Pass the configuration file or properties at the start of the command. -#### Using Gradle properties +#### Using Gradle Properties -`OPERATOR_ID`, `OPERATOR_KEY` and `HEDERA_NETWORK` must be passed as Gradle properties (`-P` parameters).\ -`HEDERA_NETWORK` can be set to `localhost`, `testnet` or `previewnet`. +Provide `OPERATOR_ID`, `OPERATOR_KEY`, and `HEDERA_NETWORK` as Gradle properties using `-P` parameters. `HEDERA_NETWORK` can be set to `localhost`, `testnet`, or `previewnet`. ```sh -./gradlew :sdk:testIntegration -POPERATOR_ID="" -POPERATOR_KEY="" -PHEDERA_NETWORK="" +./gradlew :sdk:testIntegration -POPERATOR_ID="" -POPERATOR_KEY="" -PHEDERA_NETWORK="" ``` -#### Using configuration file +#### Using a Configuration File ```sh -./gradlew :sdk:testIntegration -PCONFIG_FILE="" +./gradlew :sdk:testIntegration -PCONFIG_FILE="" ``` -An example configuration file can be found in the repo here: -[sdk/src/test/resources/client-config-with-operator.json](../../sdk/src/test/resources/client-config-with-operator.json) +An example configuration file is available in the repository at: sdk/src/test/resources/client-config-with-operator.json. -**Running against the local network** +**Running Against a Local Network** -The format of the configuration file should be as follows: +Use a configuration file in this format: ```json { @@ -63,12 +65,11 @@ The format of the configuration file should be as follows: "privateKey": "0xa608e2130a0a3cb34f86e757303c862bee353d9ab77ba4387ec084f881d420d4" } } - ``` -**Running against remote networks** +**Running Against Remote Networks** -The format of the configuration file should be as follows: +Use a configuration file in this format: ```json { @@ -80,78 +81,71 @@ The format of the configuration file should be as follows: } ``` -`HEDERA_NETWORK` can be set to `testnet`, `previewnet` or `mainnet`. +`HEDERA_NETWORK` can be set to `testnet`, `previewnet`, or `mainnet`. -#### Running individual test classes or functions +#### Running Individual Test Classes or Functions -Running test class: +To run a specific test class: ```sh -./gradlew :sdk:testIntegration -POPERATOR_ID="" -POPERATOR_KEY="" -PHEDERA_NETWORK="testnet" --tests "" +./gradlew :sdk:testIntegration -POPERATOR_ID="" -POPERATOR_KEY="" -PHEDERA_NETWORK="testnet" --tests "" ``` -Running test function: +To run a specific test function: ```sh -./gradlew :sdk:testIntegration -POPERATOR_ID="" -POPERATOR_KEY="" -PHEDERA_NETWORK="testnet" --tests "" +./gradlew :sdk:testIntegration -POPERATOR_ID="" -POPERATOR_KEY="" -PHEDERA_NETWORK="testnet" --tests "" ``` -#### Running with Intellij IDEA +#### Running with IntelliJ IDEA -1. Create a new Gradle run configuration (easiest way is to run test class or individual test function from the IDE). -2. Update "Run" configuration to pass the required Gradle properties (`OPERATOR_ID`, `OPERATOR_KEY` and `HEDERA_NETWORK`). - +1. Create a new Gradle run configuration (the easiest way is to run a test class or individual test function directly from the IDE). +2. Update the "Run" configuration to pass the required Gradle properties (`OPERATOR_ID`, `OPERATOR_KEY`, and `HEDERA_NETWORK`). -## Managing dependencies +## Managing Dependencies -This project uses a combination of Java Modules (JPMS) and Gradle to define and manage dependencies to 3rd party -libraries. In this structure, dependencies of the SDK are defined in -[sdk/src/main/java/module-info.java](../../sdk/src/main/java/module-info.java) (which is mirrored in -[sdk-full/src/main/java/module-info.java](../../sdk-full/src/main/java/module-info.java)). -Running `./gradlew qualityGate` contains a _dependency scope check_ that makes sure that both files are in sync. -Versions of 3rd party dependencies are defined in -[hiero-dependency-versions/build.gradle.kts](../../hiero-dependency-versions/build.gradle.kts). -More details about how to add/modify dependencies are found in the Hiero Gradle Conventions documentation on -[Defining modules and dependencies](https://github.com/hiero-ledger/hiero-gradle-conventions#modules). +This project uses a combination of Java Modules (JPMS) and Gradle to define and manage dependencies to third-party libraries. Dependencies for the SDK are defined in sdk/src/main/java/module-info.java (mirrored in sdk-full/src/main/java/module-info.java). Running `./gradlew qualityGate` includes a dependency scope check to ensure both files are in sync. Versions of third-party dependencies are defined in hiero-dependency-versions/build.gradle.kts. For more details on adding or modifying dependencies, see the Hiero Gradle Conventions documentation on defining modules and dependencies. -## Maintaining generated files +## Maintaining Generated Files -> Note that the below `./gradlew` commands should be run from the root of the project. +Note that all `./gradlew` commands should be run from the root of the project. -### Updating unit tests snapshots +### Updating Unit Test Snapshots ```sh ./gradlew updateSnapshots ``` -### Updating proto files +### Updating Proto Files ```sh ./gradlew updateSnapshots ``` -### Updating address books +### Updating Address Books -Update all address books: +To update all address books: ```sh ./gradlew examples:updateAddressbooks ``` -Update address books only for a mainnet: +To update address books only for mainnet: ```sh ./gradlew examples:updateAddressbooksMainnet ``` -Update address books only for a testnet: +To update address books only for testnet: ```sh ./gradlew examples:updateAddressbooksTestnet ``` -Update address books only for a previewnet: +To update address books only for previewnet: ```sh ./gradlew examples:updateAddressbooksPreviewnet ``` + +[1] https://adoptium.net \ No newline at end of file diff --git a/scripts/generate_contract_function_param_methods.py b/scripts/generate_contract_function_param_methods.py index 344cb6084c..1168d560e6 100755 --- a/scripts/generate_contract_function_param_methods.py +++ b/scripts/generate_contract_function_param_methods.py @@ -1,87 +1,71 @@ + #!/usr/bin/env python3 int_versions = [] uint_versions = [] - int_array_versions = [] uint_array_versions = [] -# does not generate 8 bit versions because those require some special treatment. - -def add_with_param_type(bit_width, param_type, map_method_name, exception_comment = ""): - int_versions.append( - "/*\n" - "* Add a " + str(bit_width) + "-bit integer.\n" - "*\n" - "* @param value The integer to be added\n" - "* @return {@code this}\n" - "*/\n" - "public ContractFunctionParameters addInt" + str(bit_width) + "(" + param_type + " value) {\n" - " args.add(new Argument(\"int" + str(bit_width) + "\", int256(value, " + str(bit_width) + "), false));\n" - "\n" - " return this;\n" - "}\n" - ) - uint_versions.append( - "/*\n" - "* Add a " + str(bit_width) + "-bit unsigned integer.\n" - "\n" - "* The value will be treated as unsigned during encoding (it will be zero-padded instead of\n" - "* sign-extended to 32 bytes).\n" - "*\n" - "* @param value The integer to be added\n" - "* @return {@code this}\n" + - exception_comment + - "*/\n" - "public ContractFunctionParameters addUint" + str(bit_width) + "(" + param_type + " value) {\n" - " args.add(new Argument(\"uint" + str(bit_width) + "\", uint256(value, " + str(bit_width) + "), false));\n" - "\n" - " return this;\n" - "}\n" - ) - int_array_versions.append( - "/**\n" - "* Add a dynamic array of " + str(bit_width) + "-bit integers.\n" - "*\n" - "* @param intArray The array of integers to be added\n" - "* @return {@code this}\n" - "*/\n" - "public ContractFunctionParameters addInt" + str(bit_width) + "Array(" + param_type + "[] intArray) {\n" - " ByteString arrayBytes = ByteString.copyFrom(\n" - " J8Arrays.stream(intArray)." + map_method_name + "(i -> int256(i, " + str(bit_width) + "))\n" - " .collect(Collectors.toList()));\n" - "\n" - " arrayBytes = uint256(intArray.length, 32).concat(arrayBytes);\n" - "\n" - " args.add(new Argument(\"int" + str(bit_width) + "[]\", arrayBytes, true));\n" - "\n" - " return this;\n" - "}\n" - ) - uint_array_versions.append( - "/**\n" - "* Add a dynamic array of " + str(bit_width) + "-bit unsigned integers.\n" - "\n" - "* The value will be treated as unsigned during encoding (it will be zero-padded instead of\n" - "* sign-extended to 32 bytes).\n" - "*\n" - "* @param intArray The array of integers to be added\n" - "* @return {@code this}\n" + - exception_comment + - "*/\n" - "public ContractFunctionParameters addUint" + str(bit_width) + "Array(" + param_type + "[] intArray) {\n" - " ByteString arrayBytes = ByteString.copyFrom(\n" - " J8Arrays.stream(intArray)." + map_method_name + "(i -> uint256(i, " + str(bit_width) + "))\n" - " .collect(Collectors.toList()));\n" - "\n" - " arrayBytes = uint256(intArray.length, 32).concat(arrayBytes);\n" - "\n" - " args.add(new Argument(\"uint" + str(bit_width) + "[]\", arrayBytes, true));\n" - "\n" - " return this;\n" - "}\n" - ) - +# Does not generate 8 bit versions because those require some special treatment. +def add_with_param_type(bit_width, param_type, map_method_name, exception_comment=""): + int_versions.append(f"""/** +* Add a {bit_width}-bit integer. +* +* @param value The integer to be added +* @return {{@code this}} +*/ +public ContractFunctionParameters addInt{bit_width}({param_type} value) {{ + args.add(new Argument(\"int{bit_width}\", int256(value, {bit_width}), false)); + return this; +}} +""") + uint_versions.append(f"""/** +* Add a {bit_width}-bit unsigned integer. +* +* The value will be treated as unsigned during encoding (it will be zero-padded instead of +* sign-extended to 32 bytes). +* +* @param value The integer to be added +* @return {{@code this}} +{exception_comment}*/ +public ContractFunctionParameters addUint{bit_width}({param_type} value) {{ + args.add(new Argument(\"uint{bit_width}\", uint256(value, {bit_width}), false)); + return this; +}} +""") + int_array_versions.append(f"""/** +* Add a dynamic array of {bit_width}-bit integers. +* +* @param intArray The array of integers to be added +* @return {{@code this}} +*/ +public ContractFunctionParameters addInt{bit_width}Array({param_type}[] intArray) {{ + ByteString arrayBytes = ByteString.copyFrom( + J8Arrays.stream(intArray).{map_method_name}(i -> int256(i, {bit_width})) + .collect(Collectors.toList())); + arrayBytes = uint256(intArray.length, 32).concat(arrayBytes); + args.add(new Argument(\"int{bit_width}[]\", arrayBytes, true)); + return this; +}} +""") + uint_array_versions.append(f"""/** +* Add a dynamic array of {bit_width}-bit unsigned integers. +* +* The value will be treated as unsigned during encoding (it will be zero-padded instead of +* sign-extended to 32 bytes). +* +* @param intArray The array of integers to be added +* @return {{@code this}} +{exception_comment}*/ +public ContractFunctionParameters addUint{bit_width}Array({param_type}[] intArray) {{ + ByteString arrayBytes = ByteString.copyFrom( + J8Arrays.stream(intArray).{map_method_name}(i -> uint256(i, {bit_width})) + .collect(Collectors.toList())); + arrayBytes = uint256(intArray.length, 32).concat(arrayBytes); + args.add(new Argument(\"uint{bit_width}[]\", arrayBytes, true)); + return this; +}} +""") for bit_width in range(16, 257, 8): if bit_width <= 32: @@ -91,26 +75,16 @@ def add_with_param_type(bit_width, param_type, map_method_name, exception_commen else: add_with_param_type(bit_width, "BigInteger", "map", "* @throws IllegalArgumentException if {@code bigInt.signum() < 0}.\n") -f = open("output.txt", "w") - -f.write("// XXXXXXXXXXXXXXXXXXXX INT VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") - -for v in int_versions: - f.write(v + "\n"); - -f.write("// XXXXXXXXXXXXXXXXXXXX INT ARRAY VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") - -for v in int_array_versions: - f.write(v + "\n"); - -f.write("// XXXXXXXXXXXXXXXXXXXX UINT VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") - -for v in uint_versions: - f.write(v + "\n"); - -f.write("// XXXXXXXXXXXXXXXXXXXX UINT ARRAY VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") - -for v in uint_array_versions: - f.write(v + "\n"); - -f.close() +with open("output.txt", "w") as f: + f.write("// XXXXXXXXXXXXXXXXXXXX INT VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") + for v in int_versions: + f.write(v + "\n") + f.write("// XXXXXXXXXXXXXXXXXXXX UINT VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") + for v in uint_versions: + f.write(v + "\n") + f.write("// XXXXXXXXXXXXXXXXXXXX INT ARRAY VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") + for v in int_array_versions: + f.write(v + "\n") + f.write("// XXXXXXXXXXXXXXXXXXXX UINT ARRAY VERSIONS XXXXXXXXXXXXXXXXXXXX\n\n") + for v in uint_array_versions: + f.write(v + "\n") diff --git a/scripts/update_protobufs.py b/scripts/update_protobufs.py index d3ad58ccfd..fbecdcb2a6 100755 --- a/scripts/update_protobufs.py +++ b/scripts/update_protobufs.py @@ -1,484 +1,553 @@ #!/usr/bin/env python3 - import os import subprocess import sys import re - - -if len(sys.argv) > 2: - print(">>> Incorrect number of arguments. Exiting.") - exit() - -if len(sys.argv) == 1: - print(">>> Usage: `" + sys.argv[0] + " ref`") - print(">>> Where \"ref\" is a valid branch or tag in the Services git repo") - -print("\n\n") - - -# make sure this is the working directory -def go_to_script_dir(): - os.chdir(os.path.dirname(__file__)) - - -go_to_script_dir() - - - - - - -PROTO_GIT_REMOTE = "https://github.com/hiero-ledger/hiero-consensus-node.git" -PROTO_GIT_PATH = os.path.join("hedera-protos-git") -PROTO_GIT_REF = sys.argv[1] if len(sys.argv)>1 else "" - - -PROTO_IN_PATH = os.path.join(PROTO_GIT_PATH, "hapi/hedera-protobuf-java-api/src/main/proto/services") -BASIC_TYPES_PATH = os.path.join(PROTO_IN_PATH, "basic_types.proto") -RESPONSE_CODE_PATH = os.path.join(PROTO_IN_PATH, "response_code.proto") -FREEZE_TYPE_PATH = os.path.join(PROTO_IN_PATH, "freeze_type.proto") - - -MAIN_PATH = os.path.join("..", "sdk", "src", "main") -PROTO_OUT_PATH = os.path.join(MAIN_PATH, "proto") -PROTO_MIRROR_OUT_PATH = os.path.join(PROTO_OUT_PATH, "mirror") -JAVA_OUT_PATH = os.path.join(MAIN_PATH, "java", "com", "hedera", "hashgraph", "sdk") -REQUEST_TYPE_OUT_PATH = os.path.join(JAVA_OUT_PATH, "RequestType.java") -STATUS_OUT_PATH = os.path.join(JAVA_OUT_PATH, "Status.java") -FEE_DATA_TYPE_OUT_PATH = os.path.join(JAVA_OUT_PATH, "FeeDataType.java") -FREEZE_TYPE_OUT_PATH = os.path.join(JAVA_OUT_PATH, "FreezeType.java") - - -PROTO_DO_NOT_REMOVE = ( -) - - -ENUM_OVERRIDE = { - "UTIL_PRNG": "PRNG" -} - - - - - - -COMMENT_REPLACEMENTS = ( - ("&", "and"), - ("", ""), - ("", "") -) - - -PROTO_REPLACEMENTS = ( - ("option java_package = \"com.hederahashgraph.api.proto.java\";", - "option java_package = \"com.hedera.hashgraph.sdk.proto\";"), - - ("option java_package = \"com.hederahashgraph.service.proto.java\";", - "option java_package = \"com.hedera.hashgraph.sdk.proto\";"), - - ("option java_package = \"com.hedera.mirror.api.proto\";", - "option java_package = \"com.hedera.hashgraph.sdk.proto.mirror\";") -) - -PROTO_REPLACEMENTS_IMPORTS = ( - # Match any import statement and captures just the part after the last / - # for example, `import "state/common.proto"` -> `import "common.proto"` - (r'import ".*\/(.*\.proto)"', - r'import "\1"'), -) - -def do_replacements(s, replacements): - for r in replacements: - s = s.replace(r[0], r[1]) - return s - -def do_replacements_proto_imports(s, replacements): - for r in replacements: - # Check if the replacement should be skipped - # Skip statements like `import "google/protobuf/wrappers.proto"` - # to update imports ONLY referred to hedera protobufs - if 'google' in s: - continue - s = re.sub(r[0], r[1], s) - return s - - - - - - -def main(): - ensure_protobufs() - print(">>> Generating RequestType.java") - generate_RequestType() - print(">>> Generating Status.java") - generate_Status() - print(">>> Generating FeeDataType.java") - generate_FeeDataType() - print(">>> Generating FreezeType.java") - generate_FreezeType() - print(">>> Clearing proto output directory") - clear_proto_dir() - print(">>> Generating modified proto files") - generate_modified_protos() - print(">>> Done") - - -def ensure_protobufs(): - if os.path.isdir(PROTO_GIT_PATH): - print(">>> Detected existing protobufs") - else: - print(">>> No protobufs detected") - run_command("git", "clone", PROTO_GIT_REMOTE, PROTO_GIT_PATH) - os.chdir(PROTO_GIT_PATH) - run_command("git", "fetch") - checkout_ref = "v0.64.0" - print(f">>> Checking out {checkout_ref}") - run_command("git", "checkout", checkout_ref) - if is_branch(checkout_ref): - run_command("git", "pull", "--rebase") - go_to_script_dir() - -def run_command(*command): - print(">>> Executing command `" + cmd_to_str(command) + "`") - if subprocess.run(command).returncode != 0: - print(">>> Return code was not 0. Exiting.") - exit() - -def cmd_to_str(command): - s = "" - for c in command: - s += (c + " ") - return s[0:-1] - -def is_branch(ref): - result = subprocess.run(['git', 'branch', '--list', ref], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if result.returncode != 0: - print(f">>> Error checking for branch {ref}: {result.stderr.decode().strip()}") - return False - return len(result.stdout.strip()) > 0 - -def get_latest_tag(): - result = subprocess.run(['git', '-c', 'versionsort.suffix=-alpha', '-c', 'versionsort.suffix=-beta', '-c', 'versionsort.suffix=-rc', 'tag', '-l', '--sort=version:refname'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if result.returncode != 0: - print(f">>> Error getting latest tag: {result.stderr.decode().strip()}") - return "" - return result.stdout.decode().strip().split('\n')[-1] - - -def generate_RequestType(): - parse_enum_from_file( - BASIC_TYPES_PATH, - "HederaFunctionality", - add_to_RequestType, - finalize_RequestType) - output_java_file(REQUEST_TYPE_OUT_PATH, RequestType_sections) - - -def generate_Status(): - parse_enum_from_file( - RESPONSE_CODE_PATH, - "ResponseCodeEnum", - add_to_Status, - finalize_Status) - output_java_file(STATUS_OUT_PATH, Status_sections) - - -def generate_FeeDataType(): - parse_enum_from_file( - BASIC_TYPES_PATH, - "SubType", - add_to_FeeDataType, - finalize_FeeDataType) - output_java_file(FEE_DATA_TYPE_OUT_PATH, FeeDataType_sections) - - - -def generate_FreezeType(): - parse_enum_from_file( - FREEZE_TYPE_PATH, - "FreezeType", - add_to_FreezeType, - finalize_FreezeType) - output_java_file(FREEZE_TYPE_OUT_PATH, FreezeType_sections) - - -def generate_TokenPauseStatus(): - parse_enum_from_file( - BASIC_TYPES_PATH, - "TokenPauseStatus", - add_to_TokenPauseStatus, - finalize_TokenPauseStatus) - output_java_file(TOKEN_PAUSE_STATUS_OUT_PATH, TokenPauseStatus_sections) - - -def clear_proto_dir(): - clear_dir(PROTO_OUT_PATH) - - -def clear_dir(dir_path): - for name in os.listdir(dir_path): - if name in PROTO_DO_NOT_REMOVE: - continue - path = os.path.join(dir_path, name) - if os.path.isfile(path): - os.unlink(path) - elif os.path.isdir(path): - clear_dir(path) - - -def generate_modified_protos(): - do_generate_modified_protos(PROTO_IN_PATH, PROTO_OUT_PATH) - -def do_generate_modified_protos(in_path, out_path): - for root, dirs, files in os.walk(in_path): - for name in files: - # for name in os.listdir(in_path): - in_file = open(os.path.join(root, name), "r") - out_file = open(os.path.join(out_path, name), "w") - file_contents_after_proto_replacements = do_replacements(in_file.read(), PROTO_REPLACEMENTS) - file_contents_after_proto_import_replacements = do_replacements_proto_imports(file_contents_after_proto_replacements, PROTO_REPLACEMENTS_IMPORTS) - out_file.write(file_contents_after_proto_import_replacements) - in_file.close() - out_file.close() - - - -def premade(name, n): - return open(os.path.join("premade", name + "-" + str(n) + ".txt"), "r").read() - - -RequestType_sections = [ - premade("RequestType", 0), - "", - premade("RequestType", 2), - "", - premade("RequestType", 4), - "", - premade("RequestType", 6) -] - - -Status_sections = [ - premade("Status", 0), - "", - premade("Status", 2), - "", - premade("Status", 4), -] - - -FeeDataType_sections = [ - premade("FeeDataType", 0), - "", - premade("FeeDataType", 2), - "", - premade("FeeDataType", 4), - "", - premade("FeeDataType", 6) -] - - -FreezeType_sections = [ - premade("FreezeType", 0), - "", - premade("FreezeType", 2), - "", - premade("FreezeType", 4) -] - - -def output_java_file(out_path, section_list): - out_file = open(out_path, "w") - for section in section_list: - out_file.write(section) - out_file.close() - -def add_to_RequestType(original_name, cap_snake_name, comment_lines): - RequestType_sections[1] += \ - generate_enum(original_name, cap_snake_name, comment_lines, "HederaFunctionality", 1) - RequestType_sections[3] += generate_valueOf(original_name, cap_snake_name, 3) - RequestType_sections[5] += generate_toString(original_name, cap_snake_name, 3) - - -def add_to_Status(original_name, cap_snake_name, comment_lines): - Status_sections[1] += \ - generate_enum(original_name, cap_snake_name, comment_lines, "ResponseCodeEnum", 1) - Status_sections[3] += generate_valueOf(original_name, cap_snake_name, 3) - - -def add_to_FeeDataType(original_name, cap_snake_name, comment_lines): - FeeDataType_sections[1] += \ - generate_enum(original_name, cap_snake_name, comment_lines, "SubType", 1) - FeeDataType_sections[3] += generate_valueOf(original_name, cap_snake_name, 3) - FeeDataType_sections[5] += generate_toString(original_name, cap_snake_name, 3) - - -def add_to_FreezeType(original_name, cap_snake_name, comment_lines): - FreezeType_sections[1] += \ - generate_enum(original_name, cap_snake_name, comment_lines, "com.hedera.hashgraph.sdk.proto.FreezeType", 1) - FreezeType_sections[3] += generate_valueOf(original_name, cap_snake_name, 3) - - -def replace_last_enum_comma(s): - return s[0:-3] + ";\n\n" - - -def finalize_RequestType(): - RequestType_sections[1] = replace_last_enum_comma(RequestType_sections[1]) - - -def finalize_Status(): - Status_sections[1] = replace_last_enum_comma(Status_sections[1]) - - -def finalize_FeeDataType(): - FeeDataType_sections[1] = replace_last_enum_comma(FeeDataType_sections[1]) - - -def finalize_FreezeType(): - FreezeType_sections[1] = replace_last_enum_comma(FreezeType_sections[1]) - - - - - - -def tabs(n): - return " "*(4*n) - - -def generate_comment(comment_lines, tab_count): - if(len(comment_lines) > 0): - retval = tabs(tab_count) + "/**\n" +import shutil +from pathlib import Path +from typing import List, Tuple, Callable, Optional + +class ProtoGenerator: + def __init__(self): + # Configuration + self.PROTO_GIT_REMOTE = "https://github.com/hiero-ledger/hiero-consensus-node.git" + self.PROTO_GIT_PATH = "hedera-protos-git" + self.DEFAULT_CHECKOUT_REF = "v0.64.0" + + # Path configurations + self.setup_paths() + + # Processing configurations + self.PROTO_DO_NOT_REMOVE = () + self.ENUM_OVERRIDE = {"UTIL_PRNG": "PRNG"} + + self.COMMENT_REPLACEMENTS = ( + ("&", "and"), + ("", ""), + ("", "") + ) + + self.PROTO_REPLACEMENTS = ( + ("option java_package = \"com.hederahashgraph.api.proto.java\";", + "option java_package = \"com.hedera.hashgraph.sdk.proto\";"), + ("option java_package = \"com.hederahashgraph.service.proto.java\";", + "option java_package = \"com.hedera.hashgraph.sdk.proto\";"), + ("option java_package = \"com.hedera.mirror.api.proto\";", + "option java_package = \"com.hedera.hashgraph.sdk.proto.mirror\";") + ) + + self.PROTO_REPLACEMENTS_IMPORTS = ( + (r'import ".*\/(.*\.proto)"', r'import "\1"'), + ) + + # Java file sections + self.setup_java_sections() + + def setup_paths(self): + """Initialize all path configurations""" + self.PROTO_IN_PATH = Path(self.PROTO_GIT_PATH) / "hapi" / "hedera-protobuf-java-api" / "src" / "main" / "proto" / "services" + self.BASIC_TYPES_PATH = self.PROTO_IN_PATH / "basic_types.proto" + self.RESPONSE_CODE_PATH = self.PROTO_IN_PATH / "response_code.proto" + self.FREEZE_TYPE_PATH = self.PROTO_IN_PATH / "freeze_type.proto" + + self.MAIN_PATH = Path("..") / "sdk" / "src" / "main" + self.PROTO_OUT_PATH = self.MAIN_PATH / "proto" + self.PROTO_MIRROR_OUT_PATH = self.PROTO_OUT_PATH / "mirror" + self.JAVA_OUT_PATH = self.MAIN_PATH / "java" / "com" / "hedera" / "hashgraph" / "sdk" + + self.REQUEST_TYPE_OUT_PATH = self.JAVA_OUT_PATH / "RequestType.java" + self.STATUS_OUT_PATH = self.JAVA_OUT_PATH / "Status.java" + self.FEE_DATA_TYPE_OUT_PATH = self.JAVA_OUT_PATH / "FeeDataType.java" + self.FREEZE_TYPE_OUT_PATH = self.JAVA_OUT_PATH / "FreezeType.java" + + def setup_java_sections(self): + """Initialize Java file section templates""" + self.RequestType_sections = [ + self.load_premade("RequestType", 0), + "", + self.load_premade("RequestType", 2), + "", + self.load_premade("RequestType", 4), + "", + self.load_premade("RequestType", 6) + ] + + self.Status_sections = [ + self.load_premade("Status", 0), + "", + self.load_premade("Status", 2), + "", + self.load_premade("Status", 4), + ] + + self.FeeDataType_sections = [ + self.load_premade("FeeDataType", 0), + "", + self.load_premade("FeeDataType", 2), + "", + self.load_premade("FeeDataType", 4), + "", + self.load_premade("FeeDataType", 6) + ] + + self.FreezeType_sections = [ + self.load_premade("FreezeType", 0), + "", + self.load_premade("FreezeType", 2), + "", + self.load_premade("FreezeType", 4) + ] + + def load_premade(self, name: str, n: int) -> str: + """Load premade template file with error handling""" + try: + premade_path = Path("premade") / f"{name}-{n}.txt" + return premade_path.read_text(encoding='utf-8') + except FileNotFoundError: + print(f">>> Warning: Premade file {premade_path} not found") + return "" + except Exception as e: + print(f">>> Error loading premade file {premade_path}: {e}") + return "" + + def go_to_script_dir(self): + """Change to the script directory""" + script_dir = Path(__file__).parent + os.chdir(script_dir) + + def run_command(self, *command) -> bool: + """Run a command with error handling""" + print(f">>> Executing command `{' '.join(command)}`") + try: + result = subprocess.run(command, check=True) + return True + except subprocess.CalledProcessError as e: + print(f">>> Command failed with return code {e.returncode}") + return False + + def is_branch(self, ref: str) -> bool: + """Check if a reference is a branch""" + try: + result = subprocess.run( + ['git', 'branch', '--list', ref], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + return result.returncode == 0 and len(result.stdout.strip()) > 0 + except Exception as e: + print(f">>> Error checking for branch {ref}: {e}") + return False + + def get_latest_tag(self) -> str: + """Get the latest git tag""" + try: + result = subprocess.run([ + 'git', '-c', 'versionsort.suffix=-alpha', + '-c', 'versionsort.suffix=-beta', + '-c', 'versionsort.suffix=-rc', + 'tag', '-l', '--sort=version:refname' + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + if result.returncode == 0: + tags = result.stdout.strip().split('\n') + return tags[-1] if tags and tags[0] else "" + return "" + except Exception as e: + print(f">>> Error getting latest tag: {e}") + return "" + + def ensure_protobufs(self, checkout_ref: Optional[str] = None): + """Ensure protobuf files are available and up to date""" + if not checkout_ref: + checkout_ref = self.DEFAULT_CHECKOUT_REF + + proto_path = Path(self.PROTO_GIT_PATH) + + if proto_path.exists(): + print(">>> Detected existing protobufs") + else: + print(">>> No protobufs detected") + if not self.run_command("git", "clone", self.PROTO_GIT_REMOTE, self.PROTO_GIT_PATH): + print(">>> Failed to clone repository. Exiting.") + sys.exit(1) + + os.chdir(proto_path) + + if not self.run_command("git", "fetch"): + print(">>> Failed to fetch updates. Continuing with existing state.") + + print(f">>> Checking out {checkout_ref}") + if not self.run_command("git", "checkout", checkout_ref): + print(f">>> Failed to checkout {checkout_ref}. Exiting.") + sys.exit(1) + + if self.is_branch(checkout_ref): + if not self.run_command("git", "pull", "--rebase"): + print(">>> Failed to pull latest changes. Continuing with current state.") + + self.go_to_script_dir() + + def do_replacements(self, s: str, replacements: Tuple[Tuple[str, str], ...]) -> str: + """Apply string replacements""" + for old, new in replacements: + s = s.replace(old, new) + return s + + def do_replacements_proto_imports(self, s: str, replacements: Tuple[Tuple[str, str], ...]) -> str: + """Apply regex replacements for proto imports""" + for pattern, replacement in replacements: + if 'google' in s: + continue + s = re.sub(pattern, replacement, s) + return s + + def clear_dir(self, dir_path: Path): + """Recursively clear directory contents""" + if not dir_path.exists(): + return + + for item in dir_path.iterdir(): + if item.name in self.PROTO_DO_NOT_REMOVE: + continue + + try: + if item.is_file(): + item.unlink() + elif item.is_dir(): + shutil.rmtree(item) + except Exception as e: + print(f">>> Warning: Failed to remove {item}: {e}") + + def generate_modified_protos(self): + """Generate modified proto files""" + self.do_generate_modified_protos(self.PROTO_IN_PATH, self.PROTO_OUT_PATH) + + def do_generate_modified_protos(self, in_path: Path, out_path: Path): + """Process proto files and apply modifications""" + if not in_path.exists(): + print(f">>> Error: Input path {in_path} does not exist") + return + + out_path.mkdir(parents=True, exist_ok=True) + + for root, dirs, files in os.walk(in_path): + for file_name in files: + if not file_name.endswith('.proto'): + continue + + try: + input_file = Path(root) / file_name + output_file = out_path / file_name + + content = input_file.read_text(encoding='utf-8') + content = self.do_replacements(content, self.PROTO_REPLACEMENTS) + content = self.do_replacements_proto_imports(content, self.PROTO_REPLACEMENTS_IMPORTS) + + output_file.write_text(content, encoding='utf-8') + + except Exception as e: + print(f">>> Error processing {file_name}: {e}") + + def output_java_file(self, out_path: Path, section_list: List[str]): + """Output Java file from sections""" + try: + out_path.parent.mkdir(parents=True, exist_ok=True) + with open(out_path, "w", encoding='utf-8') as out_file: + for section in section_list: + out_file.write(section) + except Exception as e: + print(f">>> Error writing Java file {out_path}: {e}") + + def tabs(self, n: int) -> str: + """Generate indentation""" + return " " * (4 * n) + + def generate_comment(self, comment_lines: List[str], tab_count: int) -> str: + """Generate Javadoc comment""" + if not comment_lines: + return "" + + lines = [self.tabs(tab_count) + "/**"] for line in comment_lines: - retval += tabs(tab_count) + " * " + \ - do_replacements(line, COMMENT_REPLACEMENTS) + "\n" - return retval + tabs(tab_count) + " */\n" - else: - return "" - - -def generate_enum_line(original_name, cap_snake_name, enum_name, tab_count): - return tabs(tab_count) + cap_snake_name + \ - "(" + enum_name + "." + original_name + "),\n\n" - - -def generate_enum(original_name, cap_snake_name, comment_lines, enum_name, tabs_count): - return generate_comment(comment_lines, tabs_count) + \ - generate_enum_line(original_name, cap_snake_name, enum_name, tabs_count) - - -def generate_valueOf(original_name, cap_snake_name, tabs_count): - return tabs(tabs_count) + "case " + original_name + " -> " + cap_snake_name + ";\n" - - -def generate_toString(original_name, cap_snake_name, tabs_count): - return tabs(tabs_count) + "case " + cap_snake_name + " -> " + "\"" + cap_snake_name + "\";\n" - - - -def parse_enum_from_file(in_path, enum_name, add_to_output, finalize_output): - in_file = open(in_path, "r") - s = in_file.read() - in_file.close() - enum_i = s.find("enum " + enum_name, 0) - i = s.find("{", enum_i) + 1 - comment_lines = [] - while i >= 0: - i = parse_enum_code(s, i, comment_lines, add_to_output) - finalize_output() - - -# returns -1 when end of enum reached, -# otherwise returns the index after the code that has been parsed -def parse_enum_code(s, i, comment_lines, add_to_output): - #print("parsing code", i, comment_lines) - #time.sleep(1) - equal_i = s.find("=", i) - sl_comment_i = s.find("//", i) - ml_comment_i = s.find("/*", i) - line_end_i = s.find("\n", i) - enum_end_i = s.find("}", i) - indices = [equal_i, sl_comment_i, ml_comment_i, line_end_i, enum_end_i] - indices = [index for index in indices if index >= i] - next_i = min(indices) - if next_i == equal_i: - parse_enum_line(s, i, equal_i, sl_comment_i, line_end_i, comment_lines, add_to_output) - elif next_i == sl_comment_i: - parse_sl_comment(s, sl_comment_i, line_end_i, comment_lines) - elif next_i == ml_comment_i: - return parse_ml_comment(s, ml_comment_i, comment_lines) - elif next_i == enum_end_i: - return -1 - return line_end_i + 1 - - -def parse_enum_line(s, i, equal_i, sl_comment_i, line_end_i, comment_lines, add_to_output): - if sl_comment_i > equal_i and sl_comment_i < line_end_i: - parse_sl_comment(s, sl_comment_i, line_end_i, comment_lines) - original_name = s[i:equal_i].strip() - cap_snake_name = ensure_cap_snake_name(original_name) - add_to_output(original_name, ENUM_OVERRIDE.get(cap_snake_name, cap_snake_name), comment_lines) - comment_lines.clear() - - -def parse_sl_comment(s, sl_comment_i, line_end_i, comment_lines): - comment_lines.append(s[sl_comment_i + 2:line_end_i].strip()) - - -def parse_ml_comment(s, ml_comment_i, comment_lines): - i = ml_comment_i + 2 - ml_comment_end_i = s.find("*/", i) - while i < ml_comment_end_i: - i = parse_ml_comment_line(s, i, ml_comment_end_i, comment_lines) - return ml_comment_end_i + 2 - - -def parse_ml_comment_line(s, i, ml_comment_end_i, comment_lines): - line_end_i = s.find("\n", i) - end_i = min(line_end_i, ml_comment_end_i) - stripped_line = s[i:end_i].strip() - if len(stripped_line) > 0: - if stripped_line[0] == "*": + processed_line = self.do_replacements(line, self.COMMENT_REPLACEMENTS) + lines.append(f"{self.tabs(tab_count)} * {processed_line}") + lines.append(self.tabs(tab_count) + " */") + return "\n".join(lines) + "\n" + + def generate_enum_line(self, original_name: str, cap_snake_name: str, enum_name: str, tab_count: int) -> str: + """Generate enum line""" + return f"{self.tabs(tab_count)}{cap_snake_name}({enum_name}.{original_name}),\n\n" + + def generate_enum(self, original_name: str, cap_snake_name: str, comment_lines: List[str], enum_name: str, tabs_count: int) -> str: + """Generate complete enum entry""" + return (self.generate_comment(comment_lines, tabs_count) + + self.generate_enum_line(original_name, cap_snake_name, enum_name, tabs_count)) + + def generate_valueOf(self, original_name: str, cap_snake_name: str, tabs_count: int) -> str: + """Generate valueOf case""" + return f"{self.tabs(tabs_count)}case {original_name} -> {cap_snake_name};\n" + + def generate_toString(self, original_name: str, cap_snake_name: str, tabs_count: int) -> str: + """Generate toString case""" + return f"{self.tabs(tabs_count)}case {cap_snake_name} -> \"{cap_snake_name}\";\n" + + def replace_last_enum_comma(self, s: str) -> str: + """Replace last comma with semicolon""" + return s[:-3] + ";\n\n" if s.endswith(",\n\n") else s + + def add_to_RequestType(self, original_name: str, cap_snake_name: str, comment_lines: List[str]): + """Add entry to RequestType enum""" + self.RequestType_sections[1] += self.generate_enum(original_name, cap_snake_name, comment_lines, "HederaFunctionality", 1) + self.RequestType_sections[3] += self.generate_valueOf(original_name, cap_snake_name, 3) + self.RequestType_sections[5] += self.generate_toString(original_name, cap_snake_name, 3) + + def add_to_Status(self, original_name: str, cap_snake_name: str, comment_lines: List[str]): + """Add entry to Status enum""" + self.Status_sections[1] += self.generate_enum(original_name, cap_snake_name, comment_lines, "ResponseCodeEnum", 1) + self.Status_sections[3] += self.generate_valueOf(original_name, cap_snake_name, 3) + + def add_to_FeeDataType(self, original_name: str, cap_snake_name: str, comment_lines: List[str]): + """Add entry to FeeDataType enum""" + self.FeeDataType_sections[1] += self.generate_enum(original_name, cap_snake_name, comment_lines, "SubType", 1) + self.FeeDataType_sections[3] += self.generate_valueOf(original_name, cap_snake_name, 3) + self.FeeDataType_sections[5] += self.generate_toString(original_name, cap_snake_name, 3) + + def add_to_FreezeType(self, original_name: str, cap_snake_name: str, comment_lines: List[str]): + """Add entry to FreezeType enum""" + self.FreezeType_sections[1] += self.generate_enum(original_name, cap_snake_name, comment_lines, "com.hedera.hashgraph.sdk.proto.FreezeType", 1) + self.FreezeType_sections[3] += self.generate_valueOf(original_name, cap_snake_name, 3) + + def finalize_RequestType(self): + """Finalize RequestType enum generation""" + self.RequestType_sections[1] = self.replace_last_enum_comma(self.RequestType_sections[1]) + + def finalize_Status(self): + """Finalize Status enum generation""" + self.Status_sections[1] = self.replace_last_enum_comma(self.Status_sections[1]) + + def finalize_FeeDataType(self): + """Finalize FeeDataType enum generation""" + self.FeeDataType_sections[1] = self.replace_last_enum_comma(self.FeeDataType_sections[1]) + + def finalize_FreezeType(self): + """Finalize FreezeType enum generation""" + self.FreezeType_sections[1] = self.replace_last_enum_comma(self.FreezeType_sections[1]) + + def generate_RequestType(self): + """Generate RequestType.java""" + self.parse_enum_from_file( + self.BASIC_TYPES_PATH, + "HederaFunctionality", + self.add_to_RequestType, + self.finalize_RequestType + ) + self.output_java_file(self.REQUEST_TYPE_OUT_PATH, self.RequestType_sections) + + def generate_Status(self): + """Generate Status.java""" + self.parse_enum_from_file( + self.RESPONSE_CODE_PATH, + "ResponseCodeEnum", + self.add_to_Status, + self.finalize_Status + ) + self.output_java_file(self.STATUS_OUT_PATH, self.Status_sections) + + def generate_FeeDataType(self): + """Generate FeeDataType.java""" + self.parse_enum_from_file( + self.BASIC_TYPES_PATH, + "SubType", + self.add_to_FeeDataType, + self.finalize_FeeDataType + ) + self.output_java_file(self.FEE_DATA_TYPE_OUT_PATH, self.FeeDataType_sections) + + def generate_FreezeType(self): + """Generate FreezeType.java""" + self.parse_enum_from_file( + self.FREEZE_TYPE_PATH, + "FreezeType", + self.add_to_FreezeType, + self.finalize_FreezeType + ) + self.output_java_file(self.FREEZE_TYPE_OUT_PATH, self.FreezeType_sections) + + def parse_enum_from_file(self, in_path: Path, enum_name: str, add_to_output: Callable, finalize_output: Callable): + """Parse enum from protobuf file""" + try: + if not in_path.exists(): + print(f">>> Error: File {in_path} does not exist") + return + + content = in_path.read_text(encoding='utf-8') + enum_start = content.find(f"enum {enum_name}") + if enum_start == -1: + print(f">>> Error: Enum {enum_name} not found in {in_path}") + return + + i = content.find("{", enum_start) + 1 + comment_lines = [] + + while i >= 0: + i = self.parse_enum_code(content, i, comment_lines, add_to_output) + + finalize_output() + + except Exception as e: + print(f">>> Error parsing enum from {in_path}: {e}") + + def parse_enum_code(self, s: str, i: int, comment_lines: List[str], add_to_output: Callable) -> int: + """Parse enum code section""" + equal_i = s.find("=", i) + sl_comment_i = s.find("//", i) + ml_comment_i = s.find("/*", i) + line_end_i = s.find("\n", i) + enum_end_i = s.find("}", i) + + indices = [idx for idx in [equal_i, sl_comment_i, ml_comment_i, line_end_i, enum_end_i] if idx >= i] + if not indices: + return -1 + + next_i = min(indices) + + if next_i == equal_i: + self.parse_enum_line(s, i, equal_i, sl_comment_i, line_end_i, comment_lines, add_to_output) + elif next_i == sl_comment_i: + self.parse_sl_comment(s, sl_comment_i, line_end_i, comment_lines) + elif next_i == ml_comment_i: + return self.parse_ml_comment(s, ml_comment_i, comment_lines) + elif next_i == enum_end_i: + return -1 + + return line_end_i + 1 + + def parse_enum_line(self, s: str, i: int, equal_i: int, sl_comment_i: int, line_end_i: int, comment_lines: List[str], add_to_output: Callable): + """Parse individual enum line""" + if equal_i < sl_comment_i < line_end_i: + self.parse_sl_comment(s, sl_comment_i, line_end_i, comment_lines) + + original_name = s[i:equal_i].strip() + cap_snake_name = self.ensure_cap_snake_name(original_name) + final_name = self.ENUM_OVERRIDE.get(cap_snake_name, cap_snake_name) + + add_to_output(original_name, final_name, comment_lines) + comment_lines.clear() + + def parse_sl_comment(self, s: str, sl_comment_i: int, line_end_i: int, comment_lines: List[str]): + """Parse single-line comment""" + comment_text = s[sl_comment_i + 2:line_end_i].strip() + if comment_text: + comment_lines.append(comment_text) + + def parse_ml_comment(self, s: str, ml_comment_i: int, comment_lines: List[str]) -> int: + """Parse multi-line comment""" + i = ml_comment_i + 2 + ml_comment_end_i = s.find("*/", i) + + if ml_comment_end_i == -1: + return -1 + + while i < ml_comment_end_i: + i = self.parse_ml_comment_line(s, i, ml_comment_end_i, comment_lines) + + return ml_comment_end_i + 2 + + def parse_ml_comment_line(self, s: str, i: int, ml_comment_end_i: int, comment_lines: List[str]) -> int: + """Parse single line within multi-line comment""" + line_end_i = s.find("\n", i) + if line_end_i == -1: + line_end_i = ml_comment_end_i + + end_i = min(line_end_i, ml_comment_end_i) + stripped_line = s[i:end_i].strip() + + if stripped_line and stripped_line.startswith("*"): stripped_line = stripped_line[1:].strip() - if len(stripped_line) > 0: - comment_lines.append(stripped_line) - return line_end_i + 1 - - -def id_is_next(name, i): - if (i + 1) < len(name): - return name[i:i+2] == "ID" - return False - - -def ensure_cap_snake_name(name): - # assume that name is snake-case if it contains a _ or is not mixed-case - has_underscore = "_" in name - is_not_mixed = name.isupper() or name.islower() - if has_underscore or (not has_underscore and is_not_mixed): - return name.upper() - else: - out = name[0].upper() + + if stripped_line: + comment_lines.append(stripped_line) + + return line_end_i + 1 + + def id_is_next(self, name: str, i: int) -> bool: + """Check if ID follows at position i""" + return i + 1 < len(name) and name[i:i+2] == "ID" + + def ensure_cap_snake_name(self, name: str) -> str: + """Convert name to CAPITAL_SNAKE_CASE""" + has_underscore = "_" in name + is_not_mixed = name.isupper() or name.islower() + + if has_underscore or (not has_underscore and is_not_mixed): + return name.upper() + + result = [name[0].upper()] i = 1 + while i < len(name): - if id_is_next(name, i): - out += "_ID" + if self.id_is_next(name, i): + result.append("_ID") i += 2 else: c = name[i] if c.isupper(): - out += "_" - out += c.upper() + result.append("_") + result.append(c.upper()) i += 1 - return out - - - - -main() - + + return "".join(result) + + def run(self, checkout_ref: Optional[str] = None): + """Main execution method""" + print(">>> Starting Hedera Proto Generator") + + self.go_to_script_dir() + self.ensure_protobufs(checkout_ref) + + print(">>> Generating RequestType.java") + self.generate_RequestType() + + print(">>> Generating Status.java") + self.generate_Status() + + print(">>> Generating FeeDataType.java") + self.generate_FeeDataType() + + print(">>> Generating FreezeType.java") + self.generate_FreezeType() + + print(">>> Clearing proto output directory") + self.clear_dir(self.PROTO_OUT_PATH) + + print(">>> Generating modified proto files") + self.generate_modified_protos() + + print(">>> Done") +def main(): + """Main entry point""" + if len(sys.argv) > 2: + print(">>> Incorrect number of arguments. Exiting.") + sys.exit(1) + + if len(sys.argv) == 1: + print(f">>> Usage: `{sys.argv[0]} ref`") + print(">>> Where \"ref\" is a valid branch or tag in the Services git repo") + print(">>> If no ref is provided, will use default checkout reference") + print("\n\n") + + generator = ProtoGenerator() + generator.run() + else: + checkout_ref = sys.argv[1] + print(f">>> Using checkout reference: {checkout_ref}") + print("\n\n") + + generator = ProtoGenerator() + generator.run(checkout_ref) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/AbstractTokenTransferTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/AbstractTokenTransferTransaction.java index be73b08265..3f485bdfc1 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/AbstractTokenTransferTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/AbstractTokenTransferTransaction.java @@ -172,11 +172,16 @@ public T addApprovedTokenTransferWithDecimals(TokenId tokenId, AccountId account } /** - * @param tokenId the token id - * @param accountId the account id + * Deprecated. This method is deprecated and will be removed in a future release.
+ *

+ * Please use {@link #addApprovedTokenTransfer(TokenId, AccountId, long)} instead for marking a token transfer as approved. + *

+ * + * @param tokenId the token id + * @param accountId the account id * @param isApproved whether the transfer is approved * @return {@code this} - * @deprecated - Use {@link #addApprovedTokenTransfer(TokenId, AccountId, long)} instead + * @deprecated Use {@link #addApprovedTokenTransfer(TokenId, AccountId, long)} instead. */ @Deprecated public T setTokenTransferApproval(TokenId tokenId, AccountId accountId, boolean isApproved) { @@ -244,10 +249,15 @@ public T addApprovedNftTransfer(NftId nftId, AccountId sender, AccountId receive } /** - * @param nftId the NFT id + * Deprecated. This method is deprecated and will be removed in a future release.
+ *

+ * Please use {@link #addApprovedNftTransfer(NftId, AccountId, AccountId)} instead for marking an NFT transfer as approved. + *

+ * + * @param nftId the NFT id * @param isApproved whether the transfer is approved * @return {@code this} - * @deprecated - Use {@link #addApprovedNftTransfer(NftId, AccountId, AccountId)} instead + * @deprecated Use {@link #addApprovedNftTransfer(NftId, AccountId, AccountId)} instead. */ @Deprecated public T setNftTransferApproval(NftId nftId, boolean isApproved) { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceAdjustTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceAdjustTransaction.java index 3f288e6e73..3abf5fbe97 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceAdjustTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceAdjustTransaction.java @@ -17,7 +17,13 @@ import javax.annotation.Nullable; /** - * @deprecated with no replacement + * Deprecated. This class is deprecated and will be removed in a future release.
+ *

+ * There is no direct replacement for this class. For new implementations, consider using + * {@link com.hedera.hashgraph.sdk.AccountAllowanceApproveTransaction} and related methods for managing allowances. + *

+ * + * @deprecated This class is deprecated and will be removed in a future release. Use {@link com.hedera.hashgraph.sdk.AccountAllowanceApproveTransaction} where possible. */ @Deprecated public class AccountAllowanceAdjustTransaction extends Transaction { @@ -56,12 +62,17 @@ private AccountAllowanceAdjustTransaction adjustHbarAllowance( } /** - * @deprecated - Use {@link #grantHbarAllowance(AccountId, AccountId, Hbar)} or - * {@link #revokeHbarAllowance(AccountId, AccountId, Hbar)} instead + * Deprecated. This method is deprecated and will be removed in a future release.
+ *

+ * Please use {@link #grantHbarAllowance(AccountId, AccountId, Hbar)} to grant an allowance or + * {@link #revokeHbarAllowance(AccountId, AccountId, Hbar)} to revoke an allowance instead. + *

* - * @param spenderAccountId the spender account id - * @param amount the amount of hbar - * @return an account allowance adjust transaction + * @param spenderAccountId the spender account id + * @param amount the amount of hbar + * @return an account allowance adjust transaction + * @deprecated Use {@link #grantHbarAllowance(AccountId, AccountId, Hbar)} or + * {@link #revokeHbarAllowance(AccountId, AccountId, Hbar)} instead. */ @Deprecated public AccountAllowanceAdjustTransaction addHbarAllowance(AccountId spenderAccountId, Hbar amount) { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceApproveTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceApproveTransaction.java index 3b2a187802..bd8764a36b 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceApproveTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountAllowanceApproveTransaction.java @@ -87,11 +87,15 @@ private void initFromTransactionBody() { } /** - * @deprecated - Use {@link #approveHbarAllowance(AccountId, AccountId, Hbar)} instead + * Deprecated. This method is deprecated and will be removed in a future release.
+ *

+ * Please use {@link #approveHbarAllowance(AccountId, AccountId, Hbar)} instead for granting Hbar allowance. + *

* - * @param spenderAccountId the spender account id - * @param amount the amount of hbar - * @return an account allowance approve transaction + * @param spenderAccountId the spender account id + * @param amount the amount of hbar + * @return an account allowance approve transaction + * @deprecated Use {@link #approveHbarAllowance(AccountId, AccountId, Hbar)} instead. */ @Deprecated public AccountAllowanceApproveTransaction addHbarAllowance(AccountId spenderAccountId, Hbar amount) { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountBalance.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountBalance.java index 3220bfb3ae..7864f4fedb 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountBalance.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/AccountBalance.java @@ -22,7 +22,12 @@ public class AccountBalance { public final Hbar hbars; /** - * @deprecated - Use `tokens` instead + * Deprecated. This field is deprecated and will be removed in a future release.
+ *

+ * Please use {@link #tokens} instead for accessing token balances. + *

+ * + * @deprecated Use {@link #tokens} instead. */ @Deprecated @Nonnegative