From: successli Date: Mon, 28 May 2018 02:34:46 +0000 (+0800) Subject: commit java sdk doc and source code X-Git-Url: http://git.osdn.net/view?p=bytom%2Fbytom-java-sdk.git;a=commitdiff_plain;h=924d9606f2fafc1dd515048e50f3760042021b3f commit java sdk doc and source code --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e653db --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*.iml +target/ +bytom.log diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..1dabefd --- /dev/null +++ b/doc/index.md @@ -0,0 +1,825 @@ +# Java SDK documentation + +This page will document the API classes and ways to properly use the API. +Subsequent new releases also maintain backward compatibility with this class approach. + +## Basic Usage + +``` +public static Client generateClient() throws BytomException { + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888/"; + } + return new Client(coreURL, accessToken); +} + +Client client = TestUtils.generateClient(); +``` + +## Usage + +* [`Step 1: Create a key`](#create-a-key) +* [`Step 2: Create an account`](#create-an-account) +* [`Step 3: Create an receiver`](#create-an-receiver) +* [`Step 4: Create an asset`](#create-an-asset) +* [`Step 5: Issue asset`](#issue-asset) + * [`Firstly build the transaction`](#firstly-build-the-transaction) + * [`Secondly sign the transaction`](#secondly-sign-the-transaction) + * [`Finally submit the transaction`](#finally-submit-the-transaction) + +> For more details, see [`API methods`](#api-methods) + +## Create a key + +```java +String alias = "test"; +String password = "123456"; + +Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); +Key key = Key.create(client, builder); +``` + +## Create an account + +```java +String alias = "sender-account"; +Integer quorum = 1; +List root_xpubs = new ArrayList(); +root_xpubs.add(senderKey.xpub); + +Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + +Account account = Account.create(client, builder); +``` + +## Create an receiver + +```java +String alias = receiverAccount.alias; +String id = receiverAccount.id; + +Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); +Receiver receiver = receiverBuilder.create(client); +``` + +## Create an asset + +```java + String alias = "receiver-asset"; + +List xpubs = receiverAccount.xpubs; + +Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); +receiverAsset = builder.create(client); +``` + +## Issue asset + +### Firstly build the transaction + +```java +Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); +``` + +### Secondly sign the transaction + +```java +Transaction.Template singer = new Transaction.SignerBuilder().sign(client, + controlAddress, "123456"); +``` + +### Finally submit the transaction + +```java +Transaction.SubmitResponse txs = Transaction.submit(client, singer); +``` + +---- + +## API methods + +* [`Key API`](#key-api) +* [`Account API`](#account-api) +* [`Asset API`](#asset-api) +* [`Transaction API`](#transaction-api) +* [`Wallet API`](#wallet-api) +* [`Access Token API`](#access-token-api) +* [`Block API`](#block-api) +* [`Mining API`](#mining-api) +* [`Other API`](#other-api) + +## Key API + + +#### createKey + +```java +Key create(Client client, Builder builder); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `Key.Builder` - *builder*, Builder object that builds request parameters. + +##### Returns + +- `Key` - *key*, Key object. + +---- + +#### listKeys + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `List of Key`, *List*, an ArrayList object contains Key objects. + +---- + +#### deleteKey + +```java +void delete(Client client, String xpub, String password); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `String` - *xpub*, pubkey of the key. +- `String` - *password*, password of the key. + +##### Returns + +none if the key is deleted successfully. + +---- + +#### resetKeyPassword + +```java +void resetPwd(Client client, String xpub, String oldPwd, String newPwd); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `String` - *xpub*, pubkey of the key. +- `String` - *oldPwd*, old password of the key. +- `String` - *newPwd*, new password of the key. + +##### Returns + +none if the key password is reset successfully. + + +## Account API + + +#### createAccount + +```java +Account create(Client client, Builder builder); +``` +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `Account.Builder` - *builder*, Builder object that builds request parameters. + +##### Returns + +- `Account` - *account*, Account object. + +---- + +#### listAccounts + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `List of Account`, *List*, an ArrayList object contains Account objects. + +---- + +#### deleteAccount + +```java +void delete(Client client, String account_info); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. +- `String` - *account_info*, alias or ID of account. + +##### Returns + +none if the account is deleted successfully. + +---- + +#### createAccountReceiver + +```java +Receiver create(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `Receiver` - *receiver*, Receiver object. + + +---- + +#### listAddresses + +```java +List
list(Client client); +``` + +##### Parameters + +- `Client` - *client*, Client object that makes requests to the core. + +##### Returns + +- `List of Address`, *List
*, an ArrayList object contains Address objects. + +---- + +#### validateAddress + +```java +Address validate(Client client, String address); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *address*, address of account. + +##### Returns + +- `Address` - *address*, an Address object. + + +## Asset API + + +#### createAsset + +```java +Asset create(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Asset` - *asset*, an Asset object. + +---- + +#### getAsset + +```java +Asset get(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Asset` - *asset*, an Asset object. + +---- + +#### listAssets + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Asset`, *List*, an ArrayList object contains Asset objects. + +---- + +#### updateAssetAlias + +```java +void update(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +none if the asset alias is updated success. + +---- + +#### listBalances + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Balance`, an ArrayList object contains Balance objects. + +---- + +#### listUnspentOutPuts + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of UnspentOutput`, an ArrayList object contains UnspentOutput objects. + + +## Transaction API + + +#### buildTransaction + +```java +Template build(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Template` - *template*, a template object. + +---- + +#### signTransaction + +```java +Template sign(Client client, Template template, String password); +``` + +##### Parameters + +`Object`: + +- `Client` - *Client*, Client object that makes requests to the core. +- `Template` - *template*, a template object. +- `String` - *password*, signature of the password. + +##### Returns + +- `Template` - *template*, a template object. + +---- + +#### submitTransaction + +```java +SubmitResponse submit(Client client, Template template); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `Template` - *template*, a template object. + +##### Returns + +- `SubmitResponse` - *submitResponse*, a SubmitResponse object + +---- + +#### estimateTransactionGas + +```java +TransactionGas estimateGas(Client client, Template template); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `Template` - *template*, a template object. + +##### Returns + +- `TransactionGas` - *transactionGas*, a TransactionGas object + +---- + +#### getTransaction + +```java +Transaction get(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Transaction` - *transaction*, a Transaction object + +---- + +#### listTransactions + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `List of Transaction`, *List*, an ArrayList object contains Transaction objects. + +## Wallet API + + +#### backupWallet + +```java +Wallet backupWallet(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Wallet` - *wallet*, a Wallet object + +---- + +#### restoreWallet + +```java +void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages); +``` + +##### Parameters + +`Object`: +- `Client` - *Client*, Client object that makes requests to the core. +- `Object` - *account_image*, account image. +- `Object` - *asset_image*, asset image. +- `Object` - *key_images*, key image. + +##### Returns + +none if restore wallet success. + + +## Access Token API + +```java +//example +AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client); + +List tokenList = AccessToken.list(client); + +String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799"; +AccessToken.check(client, "sheng", secret); +``` + +#### createAccessToken + +```java +AccessToken create(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `AccessToken` - *accessToken*, an AccessToken object. + +---- + +#### listAccessTokens + +```java +List list(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Array of Object`, access token array. + +---- + +#### deleteAccessToken + +```java +void delete(Client client, String id); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *id*, token ID. + +##### Returns + +none if the access token is deleted successfully. + +---- + +#### checkAccessToken + +```java +void check(Client client, String id, String secret); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *id*, token ID. +- `String` - *secret*, secret of token, the second part of the colon division for token. + +##### Returns + +none if the access token is checked valid. + + +## Block API + + +#### getBlockCount + +```java +Integer getBlockCount(Client client); +``` + +##### Parameters + +none + +##### Returns + +- `Integer` - *block_count*, recent block height of the blockchain. + +---- + +#### getBlockHash + +```java +String getBlockHash(Client client); +``` + +##### Parameters + +none + +##### Returns + +- `String` - *block_hash*, recent block hash of the blockchain. + +---- + +#### getBlock +```php +Block getBlock(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Block` - *block*, a Block object. + +---- + +#### getBlockHeader + +```java +BlockHeader getBlockHeader(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `BlockHeader` - *blockHeader*, header of block. + +---- + +#### getDifficulty + +```java +BlockDifficulty getBlockDifficulty(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `BlockDifficulty` - *blockDifficulty*, a BlockDifficulty object + +---- + +#### getHashRate + +```java +BlockHashRate getHashRate(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `BlockHashRate` - *blockHashRate*, a BlockHashRate object + +## Mining API + + +#### isMining + +```java +Boolean isMining(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Boolean` - *is_mining*, whether the node is mining. + +---- + +#### setMining + +```java +void setMining(Client client, Boolean isMining); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `Boolean` - *is_mining*, whether the node is mining. + + +## Other API + + +#### netInfo + +```java +NetInfo getNetInfo(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `NetInfo` - *netInfo*, a NetInfo object. + +---- + +#### gasRate + +```java +Gas gasRate(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Gas` - *gas*, a Gas object. + +---- + +#### verifyMessage + +```java +Boolean verifyMessage(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `Boolean` - *result*, verify result. + +---- + +#### getWork + +```java +MinerWork getWork(Client client); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. + +##### Returns + +- `MinerWork` - *minerWork*, a MinerWork object. + +---- + +#### submitWork + +```java +void submiWork(Client client, String blockHeader); +``` + +##### Parameters + +- `Client` - *Client*, Client object that makes requests to the core. +- `String` - *block_header*, raw block header. + +##### Returns + +none if the work is submitted successfully. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..610804f --- /dev/null +++ b/pom.xml @@ -0,0 +1,121 @@ + + + + 4.0.0 + + io.bytom + bytom-sdk-java + 1.0-SNAPSHOT + jar + + bytom-sdk-java + + http://www.example.com + + + + + UTF-8 + + + + + junit + junit + 4.12 + test + + + log4j + log4j + 1.2.17 + + + com.squareup.okhttp + okhttp + 2.5.0 + + + com.squareup.okhttp + mockwebserver + 2.5.0 + test + + + com.google.code.gson + gson + 2.8.0 + + + org.bouncycastle + bcpkix-jdk15on + 1.56 + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + + maven-clean-plugin + 3.0.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.7.0 + + + maven-surefire-plugin + 2.20.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 7 + 7 + + + + + diff --git a/src/main/java/io/bytom/api/AccessToken.java b/src/main/java/io/bytom/api/AccessToken.java new file mode 100644 index 0000000..d93ff2c --- /dev/null +++ b/src/main/java/io/bytom/api/AccessToken.java @@ -0,0 +1,144 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

AccessToken Class

+ */ +public class AccessToken { + /** + * Token id + */ + public String id; + /** + * Token token + */ + public String token; + /** + * Token type + */ + public String type; + /** + * create time of token + */ + @SerializedName(value = "created_at", alternate = {"create"}) + public String createTime; + + private static Logger logger = Logger.getLogger(AccessToken.class); + + /** + * Serializes the AccessToken into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the AccessToken object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + /** + * id of Token + */ + public String id; + /** + * type of Token + */ + public String type; + + public Builder() { + } + + /** + * @param id the id to set + * @return Builder + */ + public Builder setId(String id) { + this.id = id; + return this; + } + + /** + * @param type the type to set + * @return Builder + */ + public Builder setType(String type) { + this.type = type; + return this; + } + + /** + * Call create-access-token api + * + * @param client client object that makes requests to the core + * @return AccessToken object + * @throws BytomException + */ + public AccessToken create(Client client) throws BytomException { + AccessToken accessToken = client.request("create-access-token", this, AccessToken.class); + + logger.info("create-access-token:"); + logger.info(accessToken.toJson()); + + return accessToken; + } + } + + /** + * Call check-access-token api + * + * @param client client object that makes requests to the core + * @param id id + * @param secret secret + * @throws BytomException + */ + public static void check(Client client, String id, String secret) throws BytomException { + Map req = new HashMap(); + req.put("id", id); + req.put("secret", secret); + client.request("check-access-token", req); + logger.info("check-access-token."); + } + + /** + * Call delete-access-token api + * + * @param client client object that makes requests to the core + * @param id id + * @throws BytomException + */ + public static void delete(Client client, String id) throws BytomException { + Map req = new HashMap(); + req.put("id", id); + client.request("delete-access-token", req); + logger.info("delete-access-token."); + } + + /** + * Call list-access-tokens api.
+ * native method, can't rpc + * + * @param client client object that makes requests to the core + * @return list of AccessToken objects + * @throws BytomException + */ + public static List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{AccessToken.class}); + List accessTokenList = client.request("list-access-tokens", null, listType); + // TODO: 2018/5/23 need to test + logger.info("list-access-tokens:"); + logger.info("size of accessTokenList:" + accessTokenList.size()); + logger.info(accessTokenList.get(0).toJson()); + + return accessTokenList; + } + +} diff --git a/src/main/java/io/bytom/api/Account.java b/src/main/java/io/bytom/api/Account.java new file mode 100644 index 0000000..943515d --- /dev/null +++ b/src/main/java/io/bytom/api/Account.java @@ -0,0 +1,287 @@ +package io.bytom.api; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.*; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Account Class

+ */ +public class Account { + + @SerializedName("id") + public String id; + + @SerializedName("alias") + public String alias; + + @SerializedName("key_index") + public Integer key_index; + + @SerializedName("quorum") + public Integer quorum; + + @SerializedName("xpubs") + public List xpubs; + + private static Logger logger = Logger.getLogger(Account.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * create-account + * + * @param client client object that makes requests to the core + * @param builder Account.Builder to make parameters + * @return Account return a account object + * @throws BytomException BytomException + */ + public static Account create(Client client, Builder builder) throws BytomException { + Account account = client.request("create-account", builder, Account.class); + logger.info("create-account"); + logger.info(account.toString()); + return account; + } + + /** + * list-accounts + * + * @param client client object that makes requests to the core + * @return return a list of account object + * @throws BytomException BytomException + */ + public static List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Account.class}); + List accountList = client.request("list-accounts", null, listType); + logger.info("list-accounts:"); + logger.info("size of accountList:"+accountList.size()); + logger.info(accountList); + return accountList; + } + + /** + * delete-account + * @param client client object that makes requests to the core + * @param account_info account_info + * @throws BytomException BytomException + */ + public static void delete(Client client, String account_info) throws BytomException { + Map req = new HashMap<>(); + req.put("account_info", account_info); + client.request("delete-account", req); + } + + public static class Builder { + + public List root_xpubs; + + public String alias; + + public Integer quorum; + + /** + * add a xpub to root_xpubs + * + * @param xpub xpub + * @return this Builder object + */ + public Builder addRootXpub(String xpub) { + this.root_xpubs.add(xpub); + return this; + } + + /** + * set xpubs to root_xpubs + * + * @param xpubs xpubs + * @return this Builder object + */ + public Builder setRootXpub(List xpubs) { + this.root_xpubs = new ArrayList<>(xpubs); + return this; + } + + /** + * set alias to alias + * @param alias alias + * @return this Builder object + */ + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * set quorum to quorum + * + * @param quorum quorum + * @return this Builder object + */ + public Builder setQuorum(Integer quorum) { + this.quorum = quorum; + return this; + } + + } + + /** + * Use this class to create a {@link Receiver} under an account. + */ + public static class ReceiverBuilder { + + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("account_id") + public String accountId; + + /** + * Specifies the account under which the receiver is created. You must use + * this method or @{link ReceiverBuilder#setAccountId}, but not both. + * + * @param alias the unique alias of the account + * @return this ReceiverBuilder object + */ + public ReceiverBuilder setAccountAlias(String alias) { + this.accountAlias = alias; + return this; + } + + /** + * Specifies the account under which the receiver is created. You must use + * this method or @{link ReceiverBuilder#setAccountAlias}, but not both. + * + * @param id the unique ID of the account + * @return this ReceiverBuilder object + */ + public ReceiverBuilder setAccountId(String id) { + this.accountId = id; + return this; + } + + /** + * Creates a single Receiver object under an account. + * + * @param client the client object providing access to an instance of Chain Core + * @return a new Receiver object + * @throws APIException This exception is raised if the api returns errors while creating the control programs. + * @throws BadURLException This exception wraps java.net.MalformedURLException. + * @throws ConnectivityException This exception is raised if there are connectivity issues with the server. + * @throws HTTPException This exception is raised when errors occur making http requests. + * @throws JSONException This exception is raised due to malformed json requests or responses. + */ + public Receiver create(Client client) throws BytomException { + Gson gson = new Gson(); + Receiver receiver = client.request( + "create-account-receiver", this, Receiver.class); + logger.info("create-account-receiver:"); + logger.info(receiver.toJson()); + return receiver; + } + + + @Override + public String toString() { + return "ReceiverBuilder{" + + "accountAlias='" + accountAlias + '\'' + + ", accountId='" + accountId + '\'' + + '}'; + } + } + + /** + * Use this class to create a {@link Address} under an account. + */ + public static class AddressBuilder { + + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("account_id") + public String accountId; + + /** + * Specifies the account under which the address is created. You must use + * this method or @{link AddressBuilder#setAccountId}, but not both. + * + * @param alias the unique alias of the account + * @return this AddressBuilder object + */ + public AddressBuilder setAccountAlias(String alias) { + this.accountAlias = alias; + return this; + } + + /** + * Specifies the account under which the address is created. You must use + * this method or @{link AddressBuilder#setAccountAlias}, but not both. + * + * @param id the unique ID of the account + * @return this AddressBuilder object + */ + public AddressBuilder setAccountId(String id) { + this.accountId = id; + return this; + } + + /** + * list-addresses + * @param client client object that makes requests to the core + * @return list of address object + * @throws BytomException BytomException + */ + public List
list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Address.class}); + List
addressList = client.request("list-addresses", this, listType); + logger.info("list-addresses:"); + logger.info("size of addressList:" + addressList.size()); + logger.info(addressList.get(0).toJson()); + + return addressList; + } + + /** + * validate-address + * @param client client object that makes requests to the core + * @param address an address string + * @return an address object + * @throws BytomException BytomException + */ + public Address validate(Client client, String address) throws BytomException { + Map req = new HashMap<>(); + req.put("address", address); + Address addressResult = client.request("validate-address", req, Address.class); + logger.info("validate-address:"); + logger.info(addressResult.toJson()); + + return addressResult; + } + + @Override + public String toString() { + return "AddressBuilder{" + + "accountAlias='" + accountAlias + '\'' + + ", accountId='" + accountId + '\'' + + '}'; + } + } + + @Override + public String toString() { + return "Account{" + + "id='" + id + '\'' + + ", alias='" + alias + '\'' + + ", key_index=" + key_index + + ", quorum=" + quorum + + ", xpubs=" + xpubs + + '}'; + } +} diff --git a/src/main/java/io/bytom/api/Address.java b/src/main/java/io/bytom/api/Address.java new file mode 100644 index 0000000..76fd985 --- /dev/null +++ b/src/main/java/io/bytom/api/Address.java @@ -0,0 +1,50 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.JSONException; + +public class Address { + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("account_id") + public String accountId; + + @SerializedName("address") + public String address; + + @SerializedName("change") + public Boolean change; + + @SerializedName("vaild") + public Boolean vaild; + + @SerializedName("is_local") + public Boolean is_local; + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Deserializes a Address from JSON. + * + * @param json a JSON-serialized Receiver object + * @return the deserialized Receiver object + * @throws JSONException Raised if the provided string is not valid JSON. + */ + public static Address fromJson(String json) throws JSONException { + try { + return Utils.serializer.fromJson(json, Address.class); + } catch (IllegalStateException e) { + throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/io/bytom/api/Asset.java b/src/main/java/io/bytom/api/Asset.java new file mode 100644 index 0000000..3d2317e --- /dev/null +++ b/src/main/java/io/bytom/api/Asset.java @@ -0,0 +1,368 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Asset Class

+ *
+ * String - id, asset id.
+ * String - alias, name of the asset.
+ * String - issuance_program, control program of the issuance of asset.
+ * Array of Object - keys, information of asset pubkey.
+ * String - definition, definition of asset.
+ * Integer - quorum, threshold of keys that must sign a transaction to spend asset units controlled by the account.
+ */ +public class Asset { + + /** + * Globally unique identifier of the asset.
+ * Asset version 1 specifies the asset id as the hash of:
+ * - the asset version
+ * - the asset's issuance program
+ * - the core's VM version
+ * - the hash of the network's initial block + */ + public String id; + + /** + * User specified, unique identifier. + */ + public String alias; + + /** + * A program specifying a predicate to be satisfied when issuing the asset. + */ + @SerializedName(value = "issuance_program", alternate = {"issue_program"}) + public String issuanceProgram; + + /** + * The list of keys used to create the issuance program for the asset.
+ * Signatures from these keys are required for issuing units of the asset. + */ + public Key[] keys; + + @SerializedName("key_index") + public Integer keyIndex; + + @SerializedName("xpubs") + public List xpubs; + + /** + * The number of keys required to sign an issuance of the asset. + */ + @SerializedName("quorum") + public int quorum; + + /** + * User-specified, arbitrary/unstructured data visible across blockchain networks.
+ * Version 1 assets specify the definition in their issuance programs, rendering the + * definition immutable. + */ + @SerializedName("definition") + public Map definition; + + /** + * version of VM. + */ + @SerializedName("vm_version") + public int vmVersion; + + /** + * type of asset. + */ + @SerializedName("type") + public String type; + + /** + * byte of asset definition. + */ + @SerializedName("raw_definition_byte") + public String rawDefinitionByte; + + public static Logger logger = Logger.getLogger(Asset.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Key { + /** + * Hex-encoded representation of the root extended public key + */ + @SerializedName("root_xpub") + public String rootXpub; + + /** + * The derived public key, used in the asset's issuance program. + */ + @SerializedName("asset_pubkey") + public String assetPubkey; + + /** + * The derivation path of the derived key. + */ + @SerializedName("asset_derivation_path") + public String[] assetDerivationPath; + + @Override + public String toString() { + return "Key{" + + "rootXpub='" + rootXpub + '\'' + + ", assetPubkey='" + assetPubkey + '\'' + + ", assetDerivationPath=" + Arrays.toString(assetDerivationPath) + + '}'; + } + } + + /** + *

Builder Class

+ */ + public static class Builder { + /** + * User specified, unique identifier. + */ + public String alias; + + /** + * User-specified, arbitrary/unstructured data visible across blockchain networks.
+ * Version 1 assets specify the definition in their issuance programs, rendering + * the definition immutable. + */ + public Map definition; + + /** + * The list of keys used to create the issuance program for the asset.
+ * Signatures from these keys are required for issuing units of the asset.
+ * Must set with {@link #addRootXpub(String)} or + * {@link #setRootXpubs(List)} before calling {@link #create(Client)}. + */ + @SerializedName("root_xpubs") + public List rootXpubs; + + /** + * The number of keys required to sign an issuance of the asset.
+ * Must set with {@link #setQuorum(int)} before calling + * {@link #create(Client)}. + */ + public int quorum; + + /** + * Unique identifier used for request idempotence. + */ + @SerializedName("access_token") + private String access_token; + + /** + * Default constructor initializes the list of keys. + */ + public Builder() { + this.rootXpubs = new ArrayList<>(); + } + + /** + * Creates an asset object. + * + * @param client client object that makes request to the core + * @return an asset object + * @throws BytomException BytomException + */ + public Asset create(Client client) throws BytomException { + Asset asset = client.request("create-asset", this, Asset.class); + logger.info("create-asset:"); + logger.info(asset.toString()); + return asset; + } + + /** + * Sets the alias on the builder object. + * @param alias alias + * @return updated builder object + */ + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * Adds a field to the existing definition object (initializing the object if it + * doesn't exist). + * @param key key of the definition field + * @param value value of the definition field + * @return updated builder object + */ + public Builder addDefinitionField(String key, Object value) { + if (this.definition == null) { + this.definition = new HashMap<>(); + } + this.definition.put(key, value); + return this; + } + + /** + * Sets the asset definition object.
+ * Note: any existing asset definition fields will be replaced. + * @param definition asset definition object + * @return updated builder object + */ + public Builder setDefinition(Map definition) { + this.definition = definition; + return this; + } + + /** + * Sets the quorum of the issuance program. Must be called before + * {@link #create(Client)}. + * @param quorum proposed quorum + * @return updated builder object + */ + public Builder setQuorum(int quorum) { + this.quorum = quorum; + return this; + } + + /** + * Adds a key to the builder's list.
+ * Either this or {@link #setRootXpubs(List)} must be called before + * {@link #create(Client)}. + * @param xpub key + * @return updated builder object. + */ + public Builder addRootXpub(String xpub) { + this.rootXpubs.add(xpub); + return this; + } + + /** + * Sets the builder's list of keys.
+ * Note: any existing keys will be replaced.
+ * Either this or {@link #addRootXpub(String)} must be called before + * {@link #create(Client)}. + * @param xpubs list of xpubs + * @return updated builder object + */ + public Builder setRootXpubs(List xpubs) { + this.rootXpubs = new ArrayList<>(xpubs); + return this; + } + + @Override + public String toString() { + return "Builder{" + + "alias='" + alias + '\'' + + ", definition=" + definition + + ", rootXpubs=" + rootXpubs + + ", quorum=" + quorum + + ", access_token='" + access_token + '\'' + + '}'; + } + } + + /** + *

QueryBuilder Class

+ */ + public static class QueryBuilder { + + @SerializedName("id") + public String id; + + public QueryBuilder setId(String assetId) { + this.id = assetId; + return this; + } + + /** + * get-asset from bytomd + * + * @param client client object that makes requests to the core + * @return The Asset Object + * @throws BytomException BytomException + */ + public Asset get(Client client) throws BytomException { + Asset asset = client.request("get-asset", this, Asset.class); + logger.info("get-asset:"); + logger.info(asset.toJson()); + return asset; + } + + /** + * get all assets from bytomd + * + * @param client client object that makes requests to the core + * @return return list of asset object + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Asset.class}); + List assetList = client.request("list-assets", null, listType); + logger.info("list-assets:"); + logger.info("size of assetList:"+assetList.size()); + logger.info(assetList); + return assetList; + } + + } + + /** + *

AliasUpdateBuilder Class

+ */ + public static class AliasUpdateBuilder { + /** + * id of asset. + */ + @SerializedName("id") + public String id; + /** + * new alias of asset + */ + @SerializedName("alias") + public String alias; + + public AliasUpdateBuilder setAssetId(String assetId) { + this.id = assetId; + return this; + } + + public AliasUpdateBuilder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * update-asset-alias + * + * @param client client object that makes requests to the core + * @throws BytomException BytomException + */ + public void update(Client client) throws BytomException { + client.request("update-asset-alias", this); + logger.info("update-asset-alias:"); + logger.info("id:"+id); + logger.info("alias:"+alias); + } + + } + + @Override + public String toString() { + return "Asset{" + + "id='" + id + '\'' + + ", alias='" + alias + '\'' + + ", issuanceProgram='" + issuanceProgram + '\'' + + ", keys=" + Arrays.toString(keys) + + ", keyIndex=" + keyIndex + + ", xpubs=" + xpubs + + ", quorum=" + quorum + + ", definition=" + definition + + ", vmVersion=" + vmVersion + + ", type='" + type + '\'' + + ", rawDefinitionByte='" + rawDefinitionByte + '\'' + + '}'; + } +} diff --git a/src/main/java/io/bytom/api/Balance.java b/src/main/java/io/bytom/api/Balance.java new file mode 100644 index 0000000..319cbc9 --- /dev/null +++ b/src/main/java/io/bytom/api/Balance.java @@ -0,0 +1,81 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +public class Balance { + + /** + * account id + */ + @SerializedName("account_id") + public String accountId; + + /** + * name of account + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * sum of the unspent outputs. + * specified asset balance of account. + */ + public long amount; + + /** + * asset id + */ + @SerializedName("asset_id") + public String assetId; + + /** + * name of asset + */ + @SerializedName("asset_alias") + public String assetAlias; + + @SerializedName("asset_definition") + public Map definition; + + private static Logger logger = Logger.getLogger(Balance.class); + + /** + * Serializes the Balance into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + + public static class QueryBuilder { + + /** + * Call list-Balances api + * + * @param client + * @return + * @throws BytomException + */ + public List list(Client client) throws BytomException { + + // TODO: 2018/5/23 need tx and test + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Balance.class}); + List balanceList = client.request("list-balances", null, listType); + logger.info("list-balances:"); + logger.info("size of :" + balanceList.size()); + logger.info(balanceList.get(0).toJson()); + + return balanceList; + } + } +} diff --git a/src/main/java/io/bytom/api/Block.java b/src/main/java/io/bytom/api/Block.java new file mode 100644 index 0000000..cde9c07 --- /dev/null +++ b/src/main/java/io/bytom/api/Block.java @@ -0,0 +1,320 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.List; +import java.util.Map; + +public class Block { + + public Integer bits; + + public String difficulty; + + public String hash; + + public Integer height; + + public Integer nonce; + + @SerializedName("previous_block_hash") + public String previousBlockHash; + + public Integer size; + + public Integer timestamp; + + @SerializedName("transaction_merkle_root") + public String transactionsMerkleRoot; + + @SerializedName("transaction_status_hash") + public String transactionStatusHash; + + public List transactions; + + public Integer version; + + private static Logger logger = Logger.getLogger(Block.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call get-block-count api + * + * @param client + * @return + * @throws BytomException + */ + public static Integer getBlockCount(Client client) throws BytomException { + Integer blockCount = + client.requestGet("get-block-count", null, "block_count", Integer.class); + + logger.info("get-block-count:"+blockCount); + return blockCount; + } + + /** + * Call get-block-hash api + * + * @param client + * @return + * @throws BytomException + */ + public static String getBlockHash(Client client) throws BytomException { + String blockHash = + client.requestGet("get-block-hash", null, "block_hash", String.class); + + logger.info("get-block-hash:"+blockHash); + + return blockHash; + } + + public static class QueryBuilder { + + /** + * block_height, height of block. + */ + @SerializedName("block_height") + public int blockHeight; + /** + * block_hash, hash of block. + */ + @SerializedName("block_hash") + public String blockHash; + + public QueryBuilder setBlockHeight(int blockHeight) { + this.blockHeight = blockHeight; + return this; + } + + public QueryBuilder setBlockHash(String blockHash) { + this.blockHash = blockHash; + return this; + } + + /** + * Call get-block api + * + * @param client + * @return + * @throws BytomException + */ + public Block getBlock(Client client) throws BytomException { + Block block = client.request("get-block", this, Block.class); + + logger.info("get-block:"); + logger.info(block.toJson()); + + return block; + } + + /** + * Call get-block-header api + * + * @param client + * @return + * @throws BytomException + */ + public BlockHeader getBlockHeader(Client client) throws BytomException { + BlockHeader blockHeader = + client.request("get-block-header", this, BlockHeader.class); + + logger.info("get-block-header:"); + logger.info(blockHeader.toJson()); + + return blockHeader; + } + + /** + * Call get-difficulty api + * + * @param client + * @return + * @throws BytomException + */ + public BlockDifficulty getBlockDifficulty(Client client) throws BytomException { + BlockDifficulty blockDifficulty = + client.request("get-difficulty", this, BlockDifficulty.class); + + logger.info("get-difficulty:"); + logger.info(blockDifficulty.toJson()); + + return blockDifficulty; + } + + /** + * Call get-hash-rate api + * + * @param client + * @return + * @throws BytomException + */ + public BlockHashRate getHashRate(Client client) throws BytomException { + BlockHashRate blockHashRate = + client.request("get-hash-rate", this, BlockHashRate.class); + + logger.info("get-hash-rate:"); + logger.info(blockHashRate.toJson()); + + return blockHashRate; + } + + } + + public static class BlockTx { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + private String id; + + /** + * version + */ + private Integer version; + + /** + * size + */ + private Integer size; + /** + * time_range + */ + @SerializedName("time_range") + private Integer timeRange; + + /** + * status + */ + @SerializedName("status_fail") + private boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + private List inputs; + + /** + * List of specified outputs for a transaction. + */ + private List outputs; + } + + public static class AnnotatedInput { + + /** + * The number of units of the asset being issued or spent. + */ + private Integer amount; + + /** + * inputs param + */ + private String arbitrary; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private String assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + } + + public static class AnnotatedOutput { + + /** + * The number of units of the asset being controlled. + */ + private Integer amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private String assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } + + public static class BlockHeader { + + @SerializedName("block_header") + public String blockHeader; + + @SerializedName("reward") + public Integer reward; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + public static class BlockDifficulty { + public String hash; + public Integer height; + public Integer bits; + public String difficulty; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + + } + + public static class BlockHashRate { + public String hash; + public Integer height; + public Integer hash_rate; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } +} diff --git a/src/main/java/io/bytom/api/Gas.java b/src/main/java/io/bytom/api/Gas.java new file mode 100644 index 0000000..f9864db --- /dev/null +++ b/src/main/java/io/bytom/api/Gas.java @@ -0,0 +1,39 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +/** + *

Gas Class

+ */ +public class Gas { + + @SerializedName("gas_rate") + public Integer gasRate; + + private static Logger logger = Logger.getLogger(Gas.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + + /** + * Call gas-rate api + * + * @param client + * @return + * @throws BytomException + */ + public static Gas gasRate(Client client) throws BytomException { + Gas gas = client.request("gas-rate", null, Gas.class); + + logger.info("gas-rate:"); + logger.info(gas.toJson()); + + return gas; + } +} diff --git a/src/main/java/io/bytom/api/Key.java b/src/main/java/io/bytom/api/Key.java new file mode 100644 index 0000000..1c6b097 --- /dev/null +++ b/src/main/java/io/bytom/api/Key.java @@ -0,0 +1,144 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.*; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Key Class

+ * + * @version 1.0 + * @since 2018-05-18 + */ +public class Key { + + @SerializedName("alias") + public String alias; + + @SerializedName("xpub") + public String xpub; + + @SerializedName("file") + public String file; + + private static Logger logger = Logger.getLogger(Key.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Create a key object + * + * @param client client object that makes requests to the core + * @param builder Key.Builder object that make parameters + * @return Key a key object + * @throws BytomException BytomException + */ + public static Key create(Client client, Builder builder) throws BytomException { + Key key = client.request("create-key", builder, Key.class); + return key; + } + + /** + * List all key objects + * + * @param client client object that makes requests to the core + * @return a list of key object + * @throws BytomException BytomException + */ + public static List list(Client client) throws BytomException { + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Key.class}); + List keyList = client.request("list-keys", null, listType); + logger.info("list-key:"); + logger.info("size fo key:"+keyList.size()); + logger.info(keyList); + return keyList; + } + + /** + * delete a key + * + * @param client client object that makes requests to the core + * @param xpub the xpub is given when creates key + * @param password the password is given when creates key + * @throws BytomException BytomException + */ + public static void delete(Client client, String xpub, String password) throws BytomException { + Map req = new HashMap<>(); + req.put("xpub", xpub); + req.put("password", password); + client.request("delete-key", req); + } + + /** + * reset password + * + * @param client client object that makes requests to the core + * @param xpub the xpub is given when creates key + * @param oldPwd the old password is given when creates key + * @param newPwd new password used to set + * @throws BytomException BytomException + */ + public static void resetPwd(Client client, String xpub, String oldPwd, String newPwd) throws BytomException { + Map req = new HashMap<>(); + req.put("xpub", xpub); + req.put("old_password", oldPwd); + req.put("new_password", newPwd); + client.request("reset-key-password", req); + } + + /** + *

Key.Builder Class

+ */ + public static class Builder { + /** + * User specified, unique identifier. + */ + public String alias; + + /** + * User specified. + */ + public String password; + + /** + * Sets the alias on the builder object. + * + * @param alias alias + * @return updated builder object + */ + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + /** + * Sets the alias on the builder object. + * + * @param password password + * @return updated builder object + */ + public Builder setPassword(String password) { + this.password = password; + return this; + } + + } + + @Override + public String toString() { + return "Key{" + + "alias='" + alias + '\'' + + ", xpub='" + xpub + '\'' + + ", file='" + file + '\'' + + '}'; + } +} diff --git a/src/main/java/io/bytom/api/Message.java b/src/main/java/io/bytom/api/Message.java new file mode 100644 index 0000000..b55da44 --- /dev/null +++ b/src/main/java/io/bytom/api/Message.java @@ -0,0 +1,123 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +public class Message { + + @SerializedName("derived_xpub") + public String derivedXpub; + @SerializedName("signature") + public String signature; + + private static Logger logger = Logger.getLogger(Message.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class SignBuilder { + + public String address; + public String message; + public String password; + + public SignBuilder setAddress(String address) { + this.address = address; + return this; + } + + public SignBuilder setMessage(String message) { + this.message = message; + return this; + } + + public SignBuilder setPassword(String password) { + this.password = password; + return this; + } + + /** + * Call sign-message api + * + * @param client + * @return + * @throws BytomException + */ + public Message sign(Client client) throws BytomException { + Message message = client.request("sign-message", this, Message.class); + + logger.info("sign-message:"); + logger.info(message.toJson()); + + return message; + } + + } + + public static class VerifyBuilder { + + /** + * address, address for account. + */ + public String address; + + /** + * derived_xpub, derived xpub. + */ + @SerializedName("derived_xpub") + public String derivedXpub; + + /** + * message, message for signature by derived_xpub. + */ + public String message; + + /** + * signature, signature for message. + */ + public String signature; + + + public VerifyBuilder setAddress(String address) { + this.address = address; + return this; + } + + public VerifyBuilder setDerivedXpub(String derivedXpub) { + this.derivedXpub = derivedXpub; + return this; + } + + public VerifyBuilder setMessage(String message) { + this.message = message; + return this; + } + + public VerifyBuilder setSignature(String signature) { + this.signature = signature; + return this; + } + + /** + * Call verify-message api + * @param client + * @return + * @throws BytomException + */ + public Boolean verifyMessage(Client client) throws BytomException { + Boolean result = client.requestGet("verify-message", this, "result", Boolean.class); + + logger.info("verify-message:"); + logger.info(result); + + return result; + } + + } + + +} diff --git a/src/main/java/io/bytom/api/Miner.java b/src/main/java/io/bytom/api/Miner.java new file mode 100644 index 0000000..c177f3f --- /dev/null +++ b/src/main/java/io/bytom/api/Miner.java @@ -0,0 +1,92 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +public class Miner { + + private static Logger logger = Logger.getLogger(Miner.class); + + /** + * Call is-mining api + * + * @param client + * @return + * @throws BytomException + */ + public static boolean isMining(Client client) throws BytomException { + return client.requestGet("is-mining", null, "is_mining", Boolean.class); + } + + /** + * Call set-mining api + * + * @param client + * @param isMining + * @throws BytomException + */ + public static void setMining(Client client, boolean isMining) throws BytomException { + Map req = new HashMap(); + req.put("is_mining", isMining); + client.request("set-mining", req); + // TODO: 2018/5/22 + } + + /** + * Call get-work api + * + * @param client + * @return + * @throws BytomException + */ + public static MinerWork getWork(Client client) throws BytomException { + MinerWork minerWork = client.request("get-work", null, MinerWork.class); + + logger.info("get-work:"); + logger.info(minerWork.toJson()); + + return minerWork; + } + + /** + * Call submit-work api + * + * @param client + * @param blockHeader + * @throws BytomException + */ + public static void submiWork(Client client, String blockHeader) throws BytomException + { + Map req = new HashMap(); + req.put("block_header", blockHeader); + client.request("submit-work", req); + logger.info("submit-work."); + } + + public static class MinerWork { + + /** + * block_header, raw block header. + */ + @SerializedName("block_header") + public String blockHeader; + + /** + * seed, seed. + */ + @SerializedName("seed") + public String seed; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + + } +} diff --git a/src/main/java/io/bytom/api/NetInfo.java b/src/main/java/io/bytom/api/NetInfo.java new file mode 100644 index 0000000..1331e0a --- /dev/null +++ b/src/main/java/io/bytom/api/NetInfo.java @@ -0,0 +1,76 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +public class NetInfo { + + /** + * listening, whether the node is listening. + */ + public boolean listening; + + /** + * syncing, whether the node is syncing. + */ + public boolean syncing; + + /** + * mining, whether the node is mining. + */ + public boolean mining; + + /** + * peer_count, current count of connected peers. + */ + @SerializedName("peer_count") + public int peerCount; + + /** + * current_block, current block height in the node's blockchain. + */ + @SerializedName("current_block") + public long currentBlock; + + /** + * highest_block, current highest block of the connected peers. + */ + @SerializedName("highest_block") + public long highestBlock; + + /** + * network_id, network id. + */ + @SerializedName("network_id") + public String networkID; + + /** + * version, bytom version. + */ + @SerializedName("version") + public String version; + + private static Logger logger = Logger.getLogger(NetInfo.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call net-info api + * + * @param client + * @return + * @throws BytomException + */ + public static NetInfo getNetInfo(Client client) throws BytomException { + NetInfo netInfo = client.request("net-info", null, NetInfo.class); + + logger.info("net-info:"); + logger.info(netInfo.toJson()); + return netInfo; + } +} diff --git a/src/main/java/io/bytom/api/ParameterizedTypeImpl.java b/src/main/java/io/bytom/api/ParameterizedTypeImpl.java new file mode 100644 index 0000000..a706e71 --- /dev/null +++ b/src/main/java/io/bytom/api/ParameterizedTypeImpl.java @@ -0,0 +1,30 @@ +package io.bytom.api; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class ParameterizedTypeImpl implements ParameterizedType { + + private final Class raw; + private final Type[] args; + + public ParameterizedTypeImpl(Class raw, Type[] args) { + this.raw = raw; + this.args = args != null ? args : new Type[0]; + } + + @Override + public Type[] getActualTypeArguments() { + return args; + } + + @Override + public Type getRawType() { + return raw; + } + + @Override + public Type getOwnerType() { + return null; + } +} diff --git a/src/main/java/io/bytom/api/RawTransaction.java b/src/main/java/io/bytom/api/RawTransaction.java new file mode 100644 index 0000000..f09e006 --- /dev/null +++ b/src/main/java/io/bytom/api/RawTransaction.java @@ -0,0 +1,154 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RawTransaction { + /** + * version + */ + public Integer version; + /** + * size + */ + public Integer size; + /** + * time_range + */ + @SerializedName("time_range") + public Integer timeRange; + + /** + * status + */ + public Integer fee; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(RawTransaction.class); + + public String toJson() { + return Utils.serializer.toJson(this); + } + + + public static RawTransaction decode(Client client, String txId) throws BytomException { + Map req = new HashMap(); + req.put("raw_transaction", txId); + RawTransaction rawTransaction = + client.request("decode-raw-transaction", req, RawTransaction.class); + + logger.info("decode-raw-transaction:"); + logger.info(rawTransaction.toJson()); + + return rawTransaction; + } + + public static class AnnotatedInput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being issued or spent. + */ + private Integer amount; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output consumed by this input. Null if the input is an + * issuance. + */ + @SerializedName("spent_output_id") + private String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + } + + public static class AnnotatedOutput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being controlled. + */ + private Integer amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } +} diff --git a/src/main/java/io/bytom/api/Receiver.java b/src/main/java/io/bytom/api/Receiver.java new file mode 100644 index 0000000..add4a6a --- /dev/null +++ b/src/main/java/io/bytom/api/Receiver.java @@ -0,0 +1,49 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.JSONException; + +/** + * Receivers are used to facilitate payments between accounts on different + * cores. They contain a control program and an expiration date. In the future, + * more payment-related metadata may be placed here. + *

+ * Receivers are typically created under accounts via the + * {@link Account.ReceiverBuilder} class. + */ +public class Receiver { + + @SerializedName("address") + public String address; + /** + * Hex-encoded string representation of the control program. + */ + @SerializedName("control_program") + public String controlProgram; + + + /** + * Serializes the receiver into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Deserializes a Receiver from JSON. + * + * @param json a JSON-serialized Receiver object + * @return the deserialized Receiver object + * @throws JSONException Raised if the provided string is not valid JSON. + */ + public static Receiver fromJson(String json) throws JSONException { + try { + return Utils.serializer.fromJson(json, Receiver.class); + } catch (IllegalStateException e) { + throw new JSONException("Unable to parse serialized receiver: " + e.getMessage()); + } + } +} diff --git a/src/main/java/io/bytom/api/Transaction.java b/src/main/java/io/bytom/api/Transaction.java new file mode 100644 index 0000000..329c7df --- /dev/null +++ b/src/main/java/io/bytom/api/Transaction.java @@ -0,0 +1,982 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.*; + +/** + *

Transaction Class

+ */ +public class Transaction { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + @SerializedName("tx_id") + public String txId; + + /** + * Time of transaction. + */ + @SerializedName("block_time") + public String blockTime; + + /** + * Unique identifier, or block hash, of the block containing a transaction. + */ + @SerializedName("block_hash") + public String blockHash; + + /** + * Index of a transaction within the block. + */ + @SerializedName("block_index") + public String blockIndex; + + @SerializedName("block_transactions_count") + public String blockTransactionsCount; + + /** + * Height of the block containing a transaction. + */ + @SerializedName("block_height") + public int blockHeight; + + /** + * whether the state of the request has failed. + */ + @SerializedName("status_fail") + public boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + public List inputs; + + /** + * List of specified outputs for a transaction. + */ + public List outputs; + + private static Logger logger = Logger.getLogger(Transaction.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + /** + * Hex-encoded serialization of a transaction to add to the current template. + */ + @SerializedName("base_transaction") + protected String baseTransaction; + + /** + * List of actions in a transaction. + */ + protected List actions; + + /** + * A time duration in milliseconds. If the transaction is not fully signed and + * submitted within this time, it will be rejected by the blockchain. + * Additionally, any outputs reserved when building this transaction will remain + * reserved for this duration. + */ + protected long ttl; + + /** + * Call build-transaction api.
+ * + * Builds a single transaction template. + * + * @param client client object which makes requests to the server + * @return a transaction template + */ + public Template build(Client client) throws BytomException { + return client.request("build-transaction", this, Template.class); + } + + /** + * Default constructor initializes actions list. + */ + public Builder() { + this.actions = new ArrayList<>(); + } + + /** + * Sets the baseTransaction field and initializes the actions lists.
+ * This constructor can be used when executing an atomic swap and the counter + * party has sent an initialized tx template. + */ + public Builder(String baseTransaction) { + this.setBaseTransaction(baseTransaction); + this.actions = new ArrayList<>(); + } + + /** + * Sets the base transaction that will be added to the current template. + */ + public Builder setBaseTransaction(String baseTransaction) { + this.baseTransaction = baseTransaction; + return this; + } + + /** + * Adds an action to a transaction builder. + * @param action action to add + * @return updated builder object + */ + public Builder addAction(Action action) { + this.actions.add(action); + return this; + } + + /** + * Sets a transaction's time-to-live, which indicates how long outputs will be + * reserved for, and how long the transaction will remain valid. Passing zero will + * use the default TTL, which is 300000ms (5 minutes). + * @param ms the duration of the TTL, in milliseconds. + * @return updated builder object + */ + public Builder setTtl(long ms) { + this.ttl = ms; + return this; + } + } + + public static class QueryBuilder { + + public String txId; + + public QueryBuilder setTxId(String txId) { + this.txId = txId; + return this; + } + + /** + * call list-transactions api + * + * @param client client object that makes requests to the core + * @return Transaction Info + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{Transaction.class}); + List transactionList = client.request("list-transactions", this, listType); + logger.info("list-transactions:"); + logger.info("size of transactionList:" + transactionList.size()); + logger.info(transactionList.get(0).toJson()); + + return transactionList; + } + + /** + * call get-transaction api + * + * @param client + * @return + * @throws BytomException + */ + public Transaction get(Client client) throws BytomException { + // TODO: 2018/5/23 need to test + Transaction transaction = client.request("get-transaction", this, Transaction.class); + logger.info("get-transaction:"); + logger.info(transaction.toJson()); + return transaction; + } + + } + + public static class SignerBuilder { + /** + * call sign-transaction api + * + * Sends a transaction template to a remote password for signing. + * + * @param client + * @param template a signed transaction template + * @param password + * @return + * @throws BytomException + */ + public Template sign(Client client, Template template, + String password) throws BytomException { + // TODO: 2018/5/23 need to test + HashMap req = new HashMap(); + req.put("transaction", template); + req.put("password", password); + + Template templateResult = client.requestGet("sign-transaction", req, "transaction", + Transaction.Template.class); + + logger.info("sign-transaction:"); + logger.info(templateResult.toJson()); + + return templateResult; + } + + } + + /** + * A single input included in a transaction. + */ + public static class Input { + /** + * The alias of the account transferring the asset (possibly null if the input is + * an issuance or an unspent output is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * The id of the account transferring the asset (possibly null if the input is an + * issuance or an unspent output is specified). + */ + @SerializedName("account_id") + public String accountId; + + @SerializedName("address") + public String address; + + /** + * The number of units of the asset being issued or spent. + */ + public long amount; + + /** + * The alias of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The id of the output consumed by this input. Null if the input is an issuance. + */ + @SerializedName("spent_output_id") + public String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + public String type; + + public String arbitrary; + + @SerializedName("control_program") + public String controlProgram; + + } + + /** + * A single output included in a transaction. + */ + public static class Output { + /** + * The id of the output. + */ + @SerializedName("id") + public String id; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + public String type; + + /** + * The output's position in a transaction's list of outputs. + */ + public int position; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + public String controlProgram; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The alias of the asset being controlled. + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + public Map assetDefinition; + + /** + * The number of units of the asset being controlled. + */ + public long amount; + + /** + * The id of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_id") + public String accountId; + + /** + * The alias of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + @SerializedName("address") + public String address; + + } + + /** + * Base class representing actions that can be taken within a transaction. + */ + public static class Action extends HashMap { + /** + * + */ + private static final long serialVersionUID = 7948250382060074590L; + + /** + * Default constructor initializes list and sets the client token. + */ + public Action() { + // Several action types require client_token as an idempotency key. + // It's safest to include a default value for this param. + this.put("client_token", UUID.randomUUID().toString()); + } + + /** + * Represents an issuance action. + */ + public static class Issue extends Action { + /** + * + */ + private static final long serialVersionUID = -6296543909434749786L; + + /** + * Default constructor defines the action type as "issue" + */ + public Issue() { + this.put("type", "issue"); + } + + /** + * Specifies the asset to be issued using its alias.
+ * Either this or {@link Issue#setAssetId(String)} must be + * called. + * @param alias alias of the asset to be issued + * @return updated action object + */ + public Issue setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be issued using its id.
+ * Either this or {@link Issue#setAssetAlias(String)} must be + * called. + * @param id id of the asset to be issued + * @return updated action object + */ + public Issue setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be issued.
+ * Must be called. + * @param amount number of units of the asset to be issued + * @return updated action object + */ + public Issue setAmount(long amount) { + this.put("amount", amount); + return this; + } + } + + /** + * Represents a spend action taken on a particular account. + */ + public static class SpendFromAccount extends Action { + /** + * + */ + private static final long serialVersionUID = 6444162327409625893L; + + /** + * Default constructor defines the action type as "spend_account" + */ + public SpendFromAccount() { + this.put("type", "spend_account"); + } + + /** + * Specifies the spending account using its alias.
+ * Either this or {@link SpendFromAccount#setAccountId(String)} must + * be called.
+ * Must be used with {@link SpendFromAccount#setAssetAlias(String)} + * . + * @param alias alias of the spending account + * @return updated action object + */ + public SpendFromAccount setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + /** + * Specifies the spending account using its id.
+ * Either this or {@link SpendFromAccount#setAccountAlias(String)} + * must be called.
+ * Must be used with {@link SpendFromAccount#setAssetId(String)} + * . + * @param id id of the spending account + * @return updated action object + */ + public SpendFromAccount setAccountId(String id) { + this.put("account_id", id); + return this; + } + + /** + * Specifies the asset to be spent using its alias.
+ * Either this or {@link SpendFromAccount#setAssetId(String)} must be + * called.
+ * Must be used with {@link SpendFromAccount#setAccountAlias(String)} + * . + * @param alias alias of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be spent using its id.
+ * Either this or {@link SpendFromAccount#setAssetAlias(String)} must + * be called.
+ * Must be used with {@link SpendFromAccount#setAccountId(String)} + * .
+ * @param id id of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of asset to be spent.
+ * Must be called. + * @param amount number of units of the asset to be spent + * @return updated action object + */ + public SpendFromAccount setAmount(long amount) { + this.put("amount", amount); + return this; + } + } + + /** + * Represents a control action taken on a particular account. + */ + public static class ControlWithAccount extends Action { + /** + * + */ + private static final long serialVersionUID = -1067464339402520620L; + + /** + * Default constructor defines the action type as "control_account" + */ + public ControlWithAccount() { + this.put("type", "control_account"); + } + + /** + * Specifies the controlling account using its alias.
+ * Either this or {@link ControlWithAccount#setAccountId(String)} must + * be called.
+ * Must be used with {@link ControlWithAccount#setAssetAlias(String)} + * . + * @param alias alias of the controlling account + * @return updated action object + */ + public ControlWithAccount setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + /** + * Specifies the controlling account using its id.
+ * Either this or {@link ControlWithAccount#setAccountAlias(String)} + * must be called.
+ * Must be used with {@link ControlWithAccount#setAssetId(String)} + * . + * @param id id of the controlling account + * @return updated action object + */ + public ControlWithAccount setAccountId(String id) { + this.put("account_id", id); + return this; + } + + /** + * Specifies the asset to be controlled using its alias.
+ * Either this or {@link ControlWithAccount#setAssetId(String)} must + * be called.
+ * Must be used with + * {@link ControlWithAccount#setAccountAlias(String)}. + * @param alias alias of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id.
+ * Either this or {@link ControlWithAccount#setAssetAlias(String)} + * must be called.
+ * Must be used with {@link ControlWithAccount#setAccountId(String)} + * . + * @param id id of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled.
+ * Must be called. + * @param amount number of units of the asset to be controlled + * @return updated action object + */ + public ControlWithAccount setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Represents a control action taken on a particular address. + */ + public static class ControlWithAddress extends Action { + /** + * + */ + private static final long serialVersionUID = 1292007349260961499L; + + /** + * Default constructor defines the action type as "control_address" + */ + public ControlWithAddress() { + this.put("type", "control_address"); + } + + public ControlWithAddress setAddress(String address) { + this.put("address", address); + return this; + } + + /** + * Specifies the asset to be controlled using its alias.
+ * Either this or {@link ControlWithAddress#setAssetId(String)} must + * be called.
+ * @param alias alias of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id.
+ * Either this or {@link ControlWithAccount#setAssetAlias(String)} + * must be called.
+ * @param id id of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled.
+ * Must be called. + * @param amount number of units of the asset to be controlled + * @return updated action object + */ + public ControlWithAddress setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Use this action to pay assets into a {@link Receiver}. + */ + public static class ControlWithReceiver extends Action { + /** + * + */ + private static final long serialVersionUID = 7280759134960453401L; + + /** + * Default constructor. + */ + public ControlWithReceiver() { + this.put("type", "control_receiver"); + } + + /** + * Specifies the receiver that is being paid to. + * + * @param receiver the receiver being paid to + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setReceiver(Receiver receiver) { + this.put("receiver", receiver); + return this; + } + + /** + * Specifies the asset to be controlled using its alias. + *

+ * Either this or {@link ControlWithReceiver#setAssetId(String)} must + * be called. + * @param alias unique alias of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be controlled using its id. + *

+ * Either this or {@link ControlWithReceiver#setAssetAlias(String)} + * must be called. + * @param id unique ID of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + /** + * Specifies the amount of the asset to be controlled. + *

+ * Must be called. + * @param amount the number of units of the asset to be controlled + * @return this ControlWithReceiver object + */ + public ControlWithReceiver setAmount(long amount) { + this.put("amount", amount); + return this; + } + + } + + /** + * Represents a retire action. + */ + public static class Retire extends Action { + /** + * + */ + private static final long serialVersionUID = -8434272436211832706L; + + /** + * Default constructor defines the action type as "control_program" + */ + public Retire() { + this.put("type", "retire"); + } + + /** + * Specifies the amount of the asset to be retired.
+ * Must be called. + * @param amount number of units of the asset to be retired + * @return updated action object + */ + public Retire setAmount(long amount) { + this.put("amount", amount); + return this; + } + + /** + * Specifies the asset to be retired using its alias.
+ * Either this or {@link Retire#setAssetId(String)} must be + * called. + * @param alias alias of the asset to be retired + * @return updated action object + */ + public Retire setAssetAlias(String alias) { + this.put("asset_alias", alias); + return this; + } + + /** + * Specifies the asset to be retired using its id.
+ * Either this or {@link Retire#setAssetAlias(String)} must be + * called. + * @param id id of the asset to be retired + * @return updated action object + */ + public Retire setAssetId(String id) { + this.put("asset_id", id); + return this; + } + + public Retire setAccountAlias(String alias) { + this.put("account_alias", alias); + return this; + } + + public Retire setAccountId(String id) { + this.put("account_id", id); + return this; + } + + } + + /** + * Sets a k,v parameter pair. + * @param key the key on the parameter object + * @param value the corresponding value + * @return updated action object + */ + public Action setParameter(String key, Object value) { + this.put(key, value); + return this; + } + } + + public static class Template { + /** + * A hex-encoded representation of a transaction template. + */ + @SerializedName("raw_transaction") + public String rawTransaction; + + /** + * The list of signing instructions for inputs in the transaction. + */ + @SerializedName("signing_instructions") + public List signingInstructions; + + /** + * For core use only. + */ + @SerializedName("local") + private boolean local; + + /** + * False (the default) makes the transaction "final" when signing, preventing + * further changes - the signature program commits to the transaction's signature + * hash. True makes the transaction extensible, committing only to the elements in + * the transaction so far, permitting the addition of new elements. + */ + @SerializedName("allow_additional_actions") + private boolean allowAdditionalActions; + + /** + * allowAdditionalActions causes the transaction to be signed so that it can be + * used as a base transaction in a multiparty trade flow. To enable this setting, + * call this method after building the transaction, but before sending it to the + * signer. + * + * All participants in a multiparty trade flow should call this method except for + * the last signer. Do not call this option if the transaction is complete, i.e. + * if it will not be used as a base transaction. + * @return updated transaction template + */ + public Template allowAdditionalActions() { + this.allowAdditionalActions = true; + return this; + } + + /** + * A single signing instruction included in a transaction template. + */ + public static class SigningInstruction { + /** + * The input's position in a transaction's list of inputs. + */ + public int position; + + /** + * A list of components used to coordinate the signing of an input. + */ + @SerializedName("witness_components") + public WitnessComponent[] witnessComponents; + } + + /** + * A single witness component, holding information that will become the input + * witness. + */ + public static class WitnessComponent { + /** + * The type of witness component.
+ * Possible types are "data" and "raw_tx_signature". + */ + public String type; + + /** + * Data to be included in the input witness (null unless type is "data"). + */ + public String value; + + /** + * The number of signatures required for an input (null unless type is + * "signature"). + */ + public int quorum; + + /** + * The list of keys to sign with (null unless type is "signature"). + */ + public KeyID[] keys; + + /** + * The list of signatures made with the specified keys (null unless type is + * "signature"). + */ + public String[] signatures; + } + + /** + * A class representing a derived signing key. + */ + public static class KeyID { + /** + * The extended public key associated with the private key used to sign. + */ + public String xpub; + + /** + * The derivation path of the extended public key. + */ + @SerializedName("derivation_path") + public String[] derivationPath; + } + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + public static class SubmitResponse { + /** + * The transaction id. + */ + public String tx_id; + + public String toJson() { + return Utils.serializer.toJson(this); + } + + } + + /** + * Call submit-transaction api + * + * @param client + * @param template + * @return + * @throws BytomException + */ + public static SubmitResponse submit(Client client, Template template) + throws BytomException { + HashMap body = new HashMap<>(); + body.put("raw_transaction", template.rawTransaction); + return client.request("submit-transaction", body, SubmitResponse.class); + } + + public static class TransactionGas { + /** + * total consumed neu(1BTM = 10^8NEU) for execute transaction. + */ + @SerializedName("total_neu") + public int totalNeu; + + /** + * consumed neu for storage transaction . + */ + @SerializedName("storage_neu") + public int storageNeu; + /** + * consumed neu for execute VM. + */ + @SerializedName("vm_neu") + public int vmNeu; + } + + /** + * call estimate-transaction-gas api + * + * @param client + * @param template + * @return + * @throws BytomException + */ + public static TransactionGas estimateGas(Client client, Template template) + throws BytomException { + HashMap body = new HashMap<>(); + body.put("transaction_template", template); + return client.request("estimate-transaction-gas", body, TransactionGas.class); + } +} diff --git a/src/main/java/io/bytom/api/TransactionFeed.java b/src/main/java/io/bytom/api/TransactionFeed.java new file mode 100644 index 0000000..db7eefb --- /dev/null +++ b/src/main/java/io/bytom/api/TransactionFeed.java @@ -0,0 +1,159 @@ +package io.bytom.api; + +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TransactionFeed { + /** + * name of the transaction feed. + */ + public String alias; + /** + * filter, filter of the transaction feed. + */ + public String filter; + + /** + * param, param of the transaction feed. + */ + public TransactionFeedParam param; + + private static Logger logger = Logger.getLogger(Transaction.class); + + /** + * Serializes the TransactionFeed into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the TransactionFeed object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class Builder { + + public String alias; + + public String filter; + + public Builder() { + } + + public Builder setAlias(String alias) { + this.alias = alias; + return this; + } + + public Builder setFilter(String filter) { + this.filter = filter; + return this; + } + + /** + * Call create-transaction-feed api + * + * @param client + * @throws BytomException + */ + public void create(Client client) throws BytomException { + client.request("create-transaction-feed", this); + logger.info("create-transaction-feed"); + } + } + + /** + * Call get-transaction-feed api + * + * @param client + * @param alias + * @return + * @throws BytomException + */ + public static TransactionFeed get(Client client, String alias) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + + // the return param contains txfeed. + TransactionFeed transactionFeed = client.requestGet("get-transaction-feed", + req, "txfeed", TransactionFeed.class); + logger.info("get-transaction-feed."); + logger.info(transactionFeed.toJson()); + + return transactionFeed; + } + + /** + * Call update-transaction-feed api + * + * @param client + * @param alias + * @param filter + * @throws BytomException + */ + public static void update(Client client, String alias, String filter) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + req.put("filter", filter); + client.request("update-transaction-feed", req); + logger.info("update-transaction-feed"); + } + + /** + * Call list-transaction-feeds api + * @param client + * @return + * @throws BytomException + */ + public static List list(Client client) throws BytomException { + + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{TransactionFeed.class}); + List transactionFeedList = client.request("list-transaction-feeds", null, listType); + + logger.info("list-transaction-feeds:"); + logger.info("size of transactionList:" + transactionFeedList.size()); + logger.info(transactionFeedList.get(0).toJson()); + + return transactionFeedList; + } + + /** + * Call delete-transaction-feed api + * + * @param client + * @param alias + * @throws BytomException + */ + public static void delete(Client client, String alias) throws BytomException { + Map req = new HashMap(); + req.put("alias", alias); + client.request("delete-transaction-feed", req); + logger.info("delete-transaction-feed"); + } + + + + public class TransactionFeedParam { + + /** + * assetid + */ + public String assetid; + + /** + * lowerlimit + */ + public long lowerlimit; + + /** + * upperlimit + */ + public long upperlimit; + + } +} diff --git a/src/main/java/io/bytom/api/UnconfirmedTransaction.java b/src/main/java/io/bytom/api/UnconfirmedTransaction.java new file mode 100644 index 0000000..a5db29f --- /dev/null +++ b/src/main/java/io/bytom/api/UnconfirmedTransaction.java @@ -0,0 +1,197 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class UnconfirmedTransaction { + /** + * Unique identifier, or transaction hash, of a transaction. + */ + private String id; + + /** + * version + */ + private Integer version; + + /** + * size + */ + private Integer size; + /** + * time_range + */ + @SerializedName("time_range") + private Integer timeRange; + + /** + * status + */ + @SerializedName("status_fail") + private boolean statusFail; + + /** + * List of specified inputs for a transaction. + */ + private List inputs; + + /** + * List of specified outputs for a transaction. + */ + private List outputs; + + private static Logger logger = Logger.getLogger(UnconfirmedTransaction.class); + + /** + * Serializes the UnconfirmedTransaction into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the UnconfirmedTransaction object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call get-unconfirmed-transaction api + * + * @param client + * @param txId + * @return + * @throws BytomException + */ + public static UnconfirmedTransaction get(Client client, String txId) throws BytomException { + Map req = new HashMap(); + req.put("tx_id", txId); + UnconfirmedTransaction UCTX = client.request("get-unconfirmed-transaction", + req, UnconfirmedTransaction.class); + + logger.info("get-unconfirmed-transaction:"); + logger.info(UCTX.toJson()); + + return UCTX; + } + + public static UTXResponse list(Client client) throws BytomException { + UTXResponse utxResponse = + client.request("list-unconfirmed-transactions", null, UTXResponse.class); + + logger.info("list-unconfirmed-transactions:"); + logger.info(utxResponse.toJson()); + + return utxResponse; + } + + public static class UTXResponse { + + @SerializedName("total") + public Integer total; + + @SerializedName("tx_ids") + public List txIds; + + public String toJson() { + return Utils.serializer.toJson(this); + } + } + + public static class AnnotatedInput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being issued or spent. + */ + private Integer amount; + + /** + * The definition of the asset being issued or spent (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being issued or spent. + */ + @SerializedName("asset_id") + private String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output consumed by this input. Null if the input is an + * issuance. + */ + @SerializedName("spent_output_id") + private String spentOutputId; + + /** + * The type of the input.
+ * Possible values are "issue" and "spend". + */ + private String type; + } + + public static class AnnotatedOutput { + + /** + * address + */ + private String address; + + /** + * The number of units of the asset being controlled. + */ + private Integer amount; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("asset_definition") + private Map assetDefinition; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("control_program") + private String controlProgram; + + /** + * The id of the output. + */ + @SerializedName("id") + private String id; + + /** + * The output's position in a transaction's list of outputs. + */ + private Integer position; + + /** + * The type the output.
+ * Possible values are "control" and "retire". + */ + private String type; + + } +} diff --git a/src/main/java/io/bytom/api/UnspentOutput.java b/src/main/java/io/bytom/api/UnspentOutput.java new file mode 100644 index 0000000..df595c5 --- /dev/null +++ b/src/main/java/io/bytom/api/UnspentOutput.java @@ -0,0 +1,130 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.lang.reflect.Type; +import java.util.List; + +public class UnspentOutput { + /** + * The id of the account controlling this output (possibly null if a control program + * is specified). + */ + @SerializedName("account_id") + public String accountId; + + /** + * The alias of the account controlling this output (possibly null if a control + * program is specified). + */ + @SerializedName("account_alias") + public String accountAlias; + + /** + * The id of the asset being controlled. + */ + @SerializedName("asset_id") + public String assetId; + + /** + * The alias of the asset being controlled. + */ + @SerializedName("asset_alias") + public String assetAlias; + + /** + * The number of units of the asset being controlled. + */ + public long amount; + + /** + * address of account + */ + public String address; + + /** + * whether the account address is change + */ + public boolean change; + + /** + * The ID of the output. + */ + @SerializedName("id") + public String id; + + /** + * The control program which must be satisfied to transfer this output. + */ + @SerializedName("program") + public String program; + + @SerializedName("control_program_index") + public String controlProgramIndex; + + /** + * source unspent output id + */ + @SerializedName("source_id") + public String sourceId; + + /** + * position of source unspent output id in block + */ + @SerializedName("source_pos") + public int sourcePos; + + /** + * The definition of the asset being controlled (possibly null). + */ + @SerializedName("valid_height") + public int validHeight; + + private static Logger logger = Logger.getLogger(UnspentOutput.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + public static class QueryBuilder { + + /** + * id of unspent output. + */ + public String id; + + public QueryBuilder setId(String id) { + this.id = id; + return this; + } + + /** + * call list-unspent-outputs api + * + * @param client client object that makes requests to the core + * @return + * @throws BytomException BytomException + */ + public List list(Client client) throws BytomException { + + // TODO: 2018/5/21 need to tx and test + Type listType = new ParameterizedTypeImpl(List.class, new Class[]{UnspentOutput.class}); + List unspentOutputList = client.request("list-unspent-outputs", this, listType); + logger.info("list-unspent-outputs:"); + logger.info("size of unspentOutputList:" + unspentOutputList.size()); + logger.info(unspentOutputList.get(0).toJson()); + + return unspentOutputList; + } + + } +} diff --git a/src/main/java/io/bytom/api/Wallet.java b/src/main/java/io/bytom/api/Wallet.java new file mode 100644 index 0000000..5145757 --- /dev/null +++ b/src/main/java/io/bytom/api/Wallet.java @@ -0,0 +1,153 @@ +package io.bytom.api; + +import com.google.gson.annotations.SerializedName; +import io.bytom.common.Utils; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

Wallet Class

+ */ +public class Wallet { + + @SerializedName("account_image") + public AccountImage accountImage; + + @SerializedName("asset_image") + public AssetImage assetImage; + + @SerializedName("key_images") + public KeyImages keyImages; + + private static Logger logger = Logger.getLogger(Wallet.class); + + /** + * Serializes the Address into a form that is safe to transfer over the wire. + * + * @return the JSON-serialized representation of the Receiver object + */ + public String toJson() { + return Utils.serializer.toJson(this); + } + + /** + * Call backup-wallet api + * + * @param client + * @return + * @throws BytomException + */ + public static Wallet backupWallet(Client client) throws BytomException { + Wallet wallet = client.request("backup-wallet", null, Wallet.class); + + logger.info("backup-wallet:"); + logger.info(wallet.toJson()); + + return wallet; + } + + /** + * Call restore-wallet api + * + * @param client + * @param accountImage + * @param assetImage + * @param keyImages + * @throws BytomException + */ + public static void restoreWallet(Client client ,Object accountImage, Object assetImage , Object keyImages) throws BytomException{ + Map body = new HashMap(); + body.put("account_image", accountImage); + body.put("asset_image", assetImage); + body.put("key_images", keyImages); + + logger.info("restore-wallet:"); + logger.info(body.toString()); + + client.request("restore-wallet", body); + } + + public static class AccountImage { + + public Slices[] slices; + + public static class Slices { + + @SerializedName("contract_index") + public int contractIndex; + + public Account account; + + public static class Account { + + public String type; + + public List xpubs; + + public int quorum; + + @SerializedName("key_index") + public int keyIndex; + + public String id; + + public String alias; + + } + + } + } + + public static class AssetImage { + + public Assets[] assets; + + public static class Assets { + public String type; + public List xpubs; + public int quorum; + public String id; + public String alias; + public Map definition; + @SerializedName("key_index") + public int keyIndex; + @SerializedName("vm_version") + public int vmVersion; + @SerializedName("asset_image") + public String issueProgram; + @SerializedName("raw_definition_byte") + public String rawDefinitionByte; + } + } + + public static class KeyImages { + + public Xkeys[] xkeys; + + public static class Xkeys { + + public Crypto crypto; + public String id; + public String type; + public int version; + public String alias; + public String xpub; + + public static class Crypto { + public String cipher; + public String ciphertext; + public Map cipherparams; + public String kdf; + public Map kdfparams; + public String mac; + } + + } + } + +} diff --git a/src/main/java/io/bytom/common/Configuration.java b/src/main/java/io/bytom/common/Configuration.java new file mode 100644 index 0000000..21dd949 --- /dev/null +++ b/src/main/java/io/bytom/common/Configuration.java @@ -0,0 +1,29 @@ +package io.bytom.common; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +public class Configuration { + + private static Properties props = new Properties(); + static { + try { + props.load(Thread.currentThread().getContextClassLoader() + .getResourceAsStream("config.properties")); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getValue(String key) { + return props.getProperty(key); + } + + public static void updateProperties(String key, String value) { + props.setProperty(key, value); + } + +} diff --git a/src/main/java/io/bytom/common/Utils.java b/src/main/java/io/bytom/common/Utils.java new file mode 100644 index 0000000..7a7f69e --- /dev/null +++ b/src/main/java/io/bytom/common/Utils.java @@ -0,0 +1,9 @@ +package io.bytom.common; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class Utils { + public static String rfc3339DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; + public static final Gson serializer = new GsonBuilder().setDateFormat(rfc3339DateFormat).create(); +} diff --git a/src/main/java/io/bytom/exception/APIException.java b/src/main/java/io/bytom/exception/APIException.java new file mode 100644 index 0000000..52ff64d --- /dev/null +++ b/src/main/java/io/bytom/exception/APIException.java @@ -0,0 +1,169 @@ +package io.bytom.exception; + +import com.google.gson.annotations.SerializedName; + +/** + * APIException wraps errors returned by the API. + * Each error contains a brief description in addition to a unique error code.
+ * The error code can be used by Chain Support to diagnose the exact cause of the error. + * The mapping of error codes to messages is as follows:

+ * + *

General errors

+ * CH001 - Request timed out + * CH002 - Not found + * CH003 - Invalid request body + * CH004 - Invalid request header + * CH006 - Not found + * + *

Account/Asset errors

+ * CH200 - Quorum must be greater than 1 and less than or equal to the length of xpubs + * CH201 - Invalid xpub format + * CH202 - At least one xpub is required + * CH203 - Retrieved type does not match expected type + * + *

Access token errors

+ * CH300 - Malformed or empty access token id + * CH301 - Access tokens must be type client or network + * CH302 - Access token id is already in use + * CH310 - The access token used to authenticate this request cannot be deleted + * + *

Query errors

+ * CH600 - Malformed pagination parameter `after` + * CH601 - Incorrect number of parameters to filter + * CH602 - Malformed query filter + * + *

Transaction errors

+ * CH700 - Reference data does not match previous transaction's reference data
+ * CH701 - Invalid action type
+ * CH702 - Invalid alias on action
+ * CH730 - Missing raw transaction
+ * CH731 - Too many signing instructions in template for transaction
+ * CH732 - Invalid transaction input index
+ * CH733 - Invalid signature script component
+ * CH734 - Missing signature in template
+ * CH735 - Transaction rejected
+ * CH760 - Insufficient funds for tx
+ * CH761 - Some outputs are reserved; try again
+ */ +public class APIException extends BytomException { + /** + * Unique identifier for the error. + */ + public String code; + + /** + * Message describing the general nature of the error. + */ + @SerializedName("message") + public String chainMessage; + + /** + * Additional information about the error (possibly null). + */ + public String detail; + + /** + * Specifies whether the error is temporary, or a change to the request is necessary. + */ + public boolean temporary; + + /** + * Unique identifier of the request to the server. + */ + public String requestId; + + /** + * HTTP status code returned by the server. + */ + public int statusCode; + + /** + * Initializes exception with its message and requestId attributes. + * @param message error message + * @param requestId unique identifier of the request + */ + public APIException(String message, String requestId) { + super(message); + this.requestId = requestId; + } + + /** + * Intitializes exception with its code, message, detail & temporary field set. + * @param code error code + * @param message error message + * @param detail additional error information + * @param temporary unique identifier of the request + */ + public APIException(String code, String message, String detail, boolean temporary) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.temporary = temporary; + } + + /** + * Initializes exception with its code, message, detail & requestId attributes. + * @param code error code + * @param message error message + * @param detail additional error information + * @param requestId unique identifier of the request + */ + public APIException(String code, String message, String detail, String requestId) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.requestId = requestId; + } + + /** + * Initializes exception with all of its attributes. + * @param code error code + * @param message error message + * @param detail additional error information + * @param temporary specifies if the error is temporary + * @param requestId unique identifier of the request + * @param statusCode HTTP status code + */ + public APIException( + String code, + String message, + String detail, + boolean temporary, + String requestId, + int statusCode) { + super(message); + this.chainMessage = message; + this.code = code; + this.detail = detail; + this.temporary = temporary; + this.requestId = requestId; + this.statusCode = statusCode; + } + + /** + * Constructs a detailed message of the error. + * @return detailed error message + */ + @Override + public String getMessage() { + String s = ""; + + if (this.code != null && this.code.length() > 0) { + s += "Code: " + this.code + " "; + } + + s += "Message: " + this.chainMessage; + + if (this.detail != null && this.detail.length() > 0) { + s += " Detail: " + this.detail; + } + + if (this.requestId != null) { + s += " Request-ID: " + this.requestId; + } + + return s; + } +} diff --git a/src/main/java/io/bytom/exception/BadURLException.java b/src/main/java/io/bytom/exception/BadURLException.java new file mode 100644 index 0000000..dd16377 --- /dev/null +++ b/src/main/java/io/bytom/exception/BadURLException.java @@ -0,0 +1,14 @@ +package io.bytom.exception; + +/** + * BadURLException wraps errors due to malformed URLs. + */ +public class BadURLException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public BadURLException(String message) { + super(message); + } +} diff --git a/src/main/java/io/bytom/exception/BuildException.java b/src/main/java/io/bytom/exception/BuildException.java new file mode 100644 index 0000000..19a2f89 --- /dev/null +++ b/src/main/java/io/bytom/exception/BuildException.java @@ -0,0 +1,49 @@ +package io.bytom.exception; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * BuildException wraps errors returned by the build-transaction endpoint. + */ +public class BuildException extends APIException { + + public BuildException(String message, String requestId) { + super(message, requestId); + } + + public static class ActionError extends APIException { + + public static class Data { + /** + * The index of the action that caused this error. + */ + @SerializedName("index") + public Integer index; + } + + public ActionError(String message, String requestId) { + super(message, requestId); + } + + /** + * Additional data pertaining to the error. + */ + public Data data; + } + + public static class Data { + /** + * A list of errors resulting from building actions. + */ + @SerializedName("actions") + public List actionErrors; + } + + /** + * Extra data associated with this error, if any. + */ + @SerializedName("data") + public Data data; +} diff --git a/src/main/java/io/bytom/exception/BytomException.java b/src/main/java/io/bytom/exception/BytomException.java new file mode 100644 index 0000000..4043065 --- /dev/null +++ b/src/main/java/io/bytom/exception/BytomException.java @@ -0,0 +1,32 @@ +package io.bytom.exception; + +/** + * Base exception class for the Chain Core SDK. + */ +public class BytomException extends Exception { + /** + * Default constructor. + */ + public BytomException() { + super(); + } + + /** + * Initializes exception with its message attribute. + * + * @param message error message + */ + public BytomException(String message) { + super(message); + } + + /** + * Initializes a new exception while storing the original cause. + * + * @param message the error message + * @param cause the cause of the exception + */ + public BytomException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/bytom/exception/ConfigurationException.java b/src/main/java/io/bytom/exception/ConfigurationException.java new file mode 100644 index 0000000..695e873 --- /dev/null +++ b/src/main/java/io/bytom/exception/ConfigurationException.java @@ -0,0 +1,23 @@ +package io.bytom.exception; + +/** + * ConfigurationException wraps errors during client configuration. + */ +public class ConfigurationException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public ConfigurationException(String message) { + super(message); + } + + /** + * Initializes new exception while storing original cause. + * @param message the error message + * @param cause the original cause + */ + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/bytom/exception/ConnectivityException.java b/src/main/java/io/bytom/exception/ConnectivityException.java new file mode 100644 index 0000000..4f50197 --- /dev/null +++ b/src/main/java/io/bytom/exception/ConnectivityException.java @@ -0,0 +1,36 @@ +package io.bytom.exception; + +import com.squareup.okhttp.Response; + +import java.io.IOException; + +/** + * ConnectivityException wraps errors due to connectivity issues with the server. + */ +public class ConnectivityException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param resp the server response used to create error message + */ + public ConnectivityException(Response resp) { + super(formatMessage(resp)); + } + + /** + * Parses the the server response into a detailed error message. + * @param resp the server response + * @return detailed error message + */ + private static String formatMessage(Response resp) { + String s = + "Response HTTP header field Chain-Request-ID is unset. There may be network issues. Please check your local network settings."; + // TODO(kr): include client-generated reqid here once we have that. + String body; + try { + body = resp.body().string(); + } catch (IOException ex) { + body = "[unable to read response body: " + ex.toString() + "]"; + } + return String.format("%s status=%d body=%s", s, resp.code(), body); + } +} diff --git a/src/main/java/io/bytom/exception/HTTPException.java b/src/main/java/io/bytom/exception/HTTPException.java new file mode 100644 index 0000000..04646bd --- /dev/null +++ b/src/main/java/io/bytom/exception/HTTPException.java @@ -0,0 +1,23 @@ +package io.bytom.exception; + +/** + * HTTPException wraps generic HTTP errors. + */ +public class HTTPException extends BytomException { + /** + * Initializes exception with its message attribute. + * @param message error message + */ + public HTTPException(String message) { + super(message); + } + + /** + * Initializes new exception while storing original cause. + * @param message the error message + * @param cause the original cause + */ + public HTTPException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/bytom/exception/JSONException.java b/src/main/java/io/bytom/exception/JSONException.java new file mode 100644 index 0000000..a81cef6 --- /dev/null +++ b/src/main/java/io/bytom/exception/JSONException.java @@ -0,0 +1,39 @@ +package io.bytom.exception; + +/** + * JSONException wraps errors due to marshaling/unmarshaling json payloads. + */ +public class JSONException extends BytomException { + + /** + * Unique indentifier of the request to the server. + */ + public String requestId; + + /** + * Default constructor. + */ + public JSONException(String message) { + super(message); + } + + /** + * Initializes exception with its message and requestId attributes. + * Use this constructor in context of an API call. + * + * @param message error message + * @param requestId unique identifier of the request + */ + public JSONException(String message, String requestId) { + super(message); + this.requestId = requestId; + } + + public String getMessage() { + String message = "Message: " + super.getMessage(); + if (requestId != null) { + message += " Request-ID: " + requestId; + } + return message; + } +} diff --git a/src/main/java/io/bytom/http/BatchResponse.java b/src/main/java/io/bytom/http/BatchResponse.java new file mode 100644 index 0000000..da29b75 --- /dev/null +++ b/src/main/java/io/bytom/http/BatchResponse.java @@ -0,0 +1,140 @@ +package io.bytom.http; + +import io.bytom.exception.*; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.*; + +/** + * BatchResponse provides a convenient interface for handling the results of + * batched API calls. The response contains one success or error per outgoing + * request item in the batch. Errors are always of type APIExcpetion. + */ +public class BatchResponse { + private Response response; + private Map successesByIndex = new LinkedHashMap<>(); + private Map errorsByIndex = new LinkedHashMap<>(); + + /** + * This constructor is used when deserializing a response from an API call. + */ + public BatchResponse(Response response, Gson serializer, Type tClass, Type eClass) + throws BytomException, IOException { + this.response = response; + + try { + JsonArray root = new JsonParser().parse(response.body().charStream()).getAsJsonArray(); + for (int i = 0; i < root.size(); i++) { + JsonElement elem = root.get(i); + + // Test for interleaved errors + APIException err = serializer.fromJson(elem, eClass); + if (err.code != null) { + errorsByIndex.put(i, err); + continue; + } + + successesByIndex.put(i, (T) serializer.fromJson(elem, tClass)); + } + } catch (IllegalStateException e) { + throw new JSONException( + "Unable to read body: " + e.getMessage(), response.headers().get("Chain-Request-ID")); + } + } + + /** + * This constructor is used for synthetically generating a batch response + * object from a map of successes and a map of errors. It ensures that + * the successes and errors are stored in an order-preserving fashion. + */ + public BatchResponse(Map successes, Map errors) { + List successIndexes = new ArrayList<>(); + Iterator successIter = successes.keySet().iterator(); + while (successIter.hasNext()) successIndexes.add(successIter.next()); + Collections.sort(successIndexes); + for (int i : successIndexes) successesByIndex.put(i, successes.get(i)); + + List errorIndexes = new ArrayList<>(); + Iterator errorIter = errors.keySet().iterator(); + while (errorIter.hasNext()) errorIndexes.add(errorIter.next()); + Collections.sort(errorIndexes); + for (int i : errorIndexes) errorsByIndex.put(i, errors.get(i)); + } + + /** + * Returns the internal response object. + */ + public Response response() { + return response; + } + + /** + * Returns the total number of response objects. This should equal the number + * of request objects in the batch. + */ + public int size() { + return successesByIndex.size() + errorsByIndex.size(); + } + + /** + * Returns whether the request object at the given index produced a success. + * @param index the index of the request object + */ + public boolean isSuccess(int index) { + return successesByIndex.containsKey(index); + } + + /** + * Returns whether the request object at the given index produced an error. + * @param index the index of the request object + */ + public boolean isError(int index) { + return errorsByIndex.containsKey(index); + } + + /** + * Returns a list of successful response objects in the batch. The order of + * the list corresponds to the order of the request objects that produced the + * successes. + */ + public List successes() { + List res = new ArrayList<>(); + res.addAll(successesByIndex.values()); + return res; + } + + /** + * Returns a list of error objects in the batch. The order of the list + * corresponds to the order of the request objects that produced the + * errors. + */ + public List errors() { + List res = new ArrayList<>(); + res.addAll(errorsByIndex.values()); + return res; + } + + /** + * Returns a map of success responses, keyed by the index of the request + * object that produced the success. The set of this map's keys is mutually + * exclusive of the keys returned by errorsByIndex. + */ + public Map successesByIndex() { + return successesByIndex; + } + + /** + * Returns a map of error responses, keyed by the index of the request + * object that produced the error. The set of this map's keys is mutually + * exclusive of the keys returned by successByIndex. + */ + public Map errorsByIndex() { + return errorsByIndex; + } +} diff --git a/src/main/java/io/bytom/http/Client.java b/src/main/java/io/bytom/http/Client.java new file mode 100644 index 0000000..fc84d02 --- /dev/null +++ b/src/main/java/io/bytom/http/Client.java @@ -0,0 +1,788 @@ +package io.bytom.http; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import io.bytom.common.*; +import io.bytom.exception.*; +import com.google.gson.Gson; +import com.squareup.okhttp.*; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; + +import javax.net.ssl.*; +import java.io.*; +import java.lang.reflect.Type; +import java.net.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The Client object contains all information necessary to + * perform an HTTP request against a remote API. Typically, + * an application will have a client that makes requests to + * a Chain Core, and a separate Client that makes requests + * to an HSM server. + */ +public class Client { + private String url; + private AtomicInteger urlIndex; + private String accessToken; + private OkHttpClient httpClient; + + // Used to create empty, in-memory key stores. + private static final char[] DEFAULT_KEYSTORE_PASSWORD = "password".toCharArray(); + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static String version = "dev"; // updated in the static initializer + + private static class BuildProperties { + public String version; + } + + static { + InputStream in = Client.class.getClassLoader().getResourceAsStream("properties.json"); + if (in != null) { + InputStreamReader inr = new InputStreamReader(in); + version = Utils.serializer.fromJson(inr, BuildProperties.class).version; + } + } + + public Client(Builder builder) throws ConfigurationException { + this.urlIndex = new AtomicInteger(0); + this.url = builder.url; + this.accessToken = builder.accessToken; + this.httpClient = buildHttpClient(builder); + } + + /** + * Create a new http Client object using the default development host URL. + */ + public Client() throws BytomException { + this(new Builder()); + } + + /** + * Create a new http Client object + * + * @param url the URL of the Chain Core or HSM + */ + public Client(String url) throws BytomException { + this(new Builder().setUrl(url)); + } + + /** + * Create a new http Client object + * + * @param url the URL of the Chain Core or HSM + * @param accessToken a Client API access token + */ + public Client(String url, String accessToken) throws BytomException { + this(new Builder().setUrl(url).setAccessToken(accessToken)); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public T request(String action, Object body, final Type tClass) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } else if (data != null) { + return deserializer.fromJson(data, tClass); + } else { + return deserializer.fromJson(response.body().charStream(), tClass); + } + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action, + * ignoring the body of the response. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @throws BytomException + */ + public void request(String action, Object body) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public Object create(Response response, Gson deserializer) throws IOException, BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + if (status != null && status.toString().contains("fail")) { + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + } + return null; + } + }; + post(action, body, rc); + } + + /** + * return the value of named as key from json + * + * @param action + * @param body + * @param key + * @param tClass + * @param + * @return + * @throws BytomException + */ + public T requestGet(String action, Object body, final String key, final Type tClass) + throws BytomException { + ResponseCreator rc = new ResponseCreator() { + public T create(Response response, Gson deserializer) throws IOException, + BytomException { + JsonElement root = new JsonParser().parse(response.body().charStream()); + JsonElement status = root.getAsJsonObject().get("status"); + JsonElement data = root.getAsJsonObject().get("data"); + + if (status != null && status.toString().contains("fail")) + throw new BytomException(root.getAsJsonObject().get("msg").toString()); + else if (data != null) + return deserializer.fromJson(data.getAsJsonObject().get(key), tClass); + else + return deserializer.fromJson(response.body().charStream(), tClass); + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * Use this method if you want batch semantics, i.e., the endpoint response + * is an array of valid objects interleaved with arrays, once corresponding to + * each input object. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @param eClass Type of error object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public BatchResponse batchRequest( + String action, Object body, final Type tClass, final Type eClass) throws BytomException { + ResponseCreator> rc = + new ResponseCreator>() { + public BatchResponse create(Response response, Gson deserializer) + throws BytomException, IOException { + return new BatchResponse<>(response, deserializer, tClass, eClass); + } + }; + return post(action, body, rc); + } + + /** + * Perform a single HTTP POST request against the API for a specific action. + * Use this method if you want single-item semantics (creating single assets, + * building single transactions) but the API endpoint is implemented as a + * batch call. + *

+ * Because request bodies for batch calls do not share a consistent format, + * this method does not perform any automatic arrayification of outgoing + * parameters. Remember to arrayify your request objects where appropriate. + * + * @param action The requested API action + * @param body Body payload sent to the API as JSON + * @param tClass Type of object to be deserialized from the response JSON + * @return the result of the post request + * @throws BytomException + */ + public T singletonBatchRequest( + String action, Object body, final Type tClass, final Type eClass) throws BytomException { + ResponseCreator rc = + new ResponseCreator() { + public T create(Response response, Gson deserializer) throws BytomException, IOException { + BatchResponse batch = new BatchResponse<>(response, deserializer, tClass, eClass); + + List errors = batch.errors(); + if (errors.size() == 1) { + // This throw must occur within this lambda in order for APIClient's + // retry logic to take effect. + throw errors.get(0); + } + + List successes = batch.successes(); + if (successes.size() == 1) { + return successes.get(0); + } + + // We should never get here, unless there is a bug in either the SDK or + // API code, causing a non-singleton response. + /* + throw new BytomException( + "Invalid singleton response, request ID " + + batch.response().headers().get("Bytom-Request-ID")); + */ + throw new BytomException("Invalid singleton response."); + } + }; + return post(action, body, rc); + } + + /** + * Returns true if a client access token stored in the client. + * + * @return a boolean + */ + public boolean hasAccessToken() { + return this.accessToken != null && !this.accessToken.isEmpty(); + } + + /** + * Returns the client access token (possibly null). + * + * @return the client access token + */ + public String accessToken() { + return accessToken; + } + + /** + * Pins a public key to the HTTP client. + * + * @param provider certificate provider + * @param subjPubKeyInfoHash public key hash + */ + public void pinCertificate(String provider, String subjPubKeyInfoHash) { + CertificatePinner cp = + new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); + this.httpClient.setCertificatePinner(cp); + } + + /** + * Sets the default connect timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setConnectTimeout(long timeout, TimeUnit unit) { + this.httpClient.setConnectTimeout(timeout, unit); + } + + /** + * Sets the default read timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setReadTimeout(long timeout, TimeUnit unit) { + this.httpClient.setReadTimeout(timeout, unit); + } + + /** + * Sets the default write timeout for new connections. A value of 0 means no timeout. + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public void setWriteTimeout(long timeout, TimeUnit unit) { + this.httpClient.setWriteTimeout(timeout, unit); + } + + /** + * Sets the proxy information for the HTTP client. + * + * @param proxy proxy object + */ + public void setProxy(Proxy proxy) { + this.httpClient.setProxy(proxy); + } + + /** + * Defines an interface for deserializing HTTP responses into objects. + * + * @param the type of object to return + */ + public interface ResponseCreator { + /** + * Deserializes an HTTP response into a Java object of type T. + * + * @param response HTTP response object + * @param deserializer json deserializer + * @return an object of type T + * @throws BytomException + * @throws IOException + */ + T create(Response response, Gson deserializer) throws BytomException, IOException; + } + + /** + * Builds and executes an HTTP Post request. + * + * @param path the path to the endpoint + * @param body the request body + * @param respCreator object specifying the response structure + * @return a response deserialized into type T + * @throws BytomException + */ + private T post(String path, Object body, ResponseCreator respCreator) + throws BytomException { + + RequestBody requestBody = RequestBody.create(this.JSON, Utils.serializer.toJson(body)); + Request req; + + BytomException exception = null; + URL endpointURL = null; + + try { + endpointURL = new URL(url + "/" + path); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + Request.Builder builder = + new Request.Builder() + .header("User-Agent", "bytom-sdk-java/" + version) + .url(endpointURL) + .method("POST", requestBody); + if (hasAccessToken()) { + builder = builder.header("Authorization", buildCredentials()); + } + req = builder.build(); + + Response resp = null; + + T object = null; + + try { + resp = this.checkError(this.httpClient.newCall(req).execute()); + object = respCreator.create(resp, Utils.serializer); + } catch (IOException e) { + e.printStackTrace(); + } + + return object; + } + + private OkHttpClient buildHttpClient(Builder builder) throws ConfigurationException { + OkHttpClient httpClient = builder.baseHttpClient.clone(); + + try { + if (builder.trustManagers != null) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(builder.keyManagers, builder.trustManagers, null); + httpClient.setSslSocketFactory(sslContext.getSocketFactory()); + } + } catch (GeneralSecurityException ex) { + throw new ConfigurationException("Unable to configure TLS", ex); + } + if (builder.readTimeoutUnit != null) { + httpClient.setReadTimeout(builder.readTimeout, builder.readTimeoutUnit); + } + if (builder.writeTimeoutUnit != null) { + httpClient.setWriteTimeout(builder.writeTimeout, builder.writeTimeoutUnit); + } + if (builder.connectTimeoutUnit != null) { + httpClient.setConnectTimeout(builder.connectTimeout, builder.connectTimeoutUnit); + } + if (builder.pool != null) { + httpClient.setConnectionPool(builder.pool); + } + if (builder.proxy != null) { + httpClient.setProxy(builder.proxy); + } + if (builder.cp != null) { + httpClient.setCertificatePinner(builder.cp); + } + + return httpClient; + } + + private static final Random randomGenerator = new Random(); + private static final int MAX_RETRIES = 10; + private static final int RETRY_BASE_DELAY_MILLIS = 40; + + // the max amount of time cored leader election could take + private static final int RETRY_MAX_DELAY_MILLIS = 15000; + + private static int retryDelayMillis(int retryAttempt) { + // Calculate the max delay as base * 2 ^ (retryAttempt - 1). + int max = RETRY_BASE_DELAY_MILLIS * (1 << (retryAttempt - 1)); + max = Math.min(max, RETRY_MAX_DELAY_MILLIS); + + // To incorporate jitter, use a pseudo random delay between [max/2, max] millis. + return randomGenerator.nextInt(max / 2) + max / 2 + 1; + } + + private static final int[] RETRIABLE_STATUS_CODES = { + 408, // Request Timeout + 429, // Too Many Requests + 500, // Internal Server Error + 502, // Bad Gateway + 503, // Service Unavailable + 504, // Gateway Timeout + 509, // Bandwidth Limit Exceeded + }; + + private static boolean isRetriableStatusCode(int statusCode) { + for (int i = 0; i < RETRIABLE_STATUS_CODES.length; i++) { + if (RETRIABLE_STATUS_CODES[i] == statusCode) { + return true; + } + } + return false; + } + + private Response checkError(Response response) throws BytomException { + /* + String rid = response.headers().get("Bytom-Request-ID"); + if (rid == null || rid.length() == 0) { + // Header field Bytom-Request-ID is set by the backend + // API server. If this field is set, then we can expect + // the body to be well-formed JSON. If it's not set, + // then we are probably talking to a gateway or proxy. + throw new ConnectivityException(response); + } */ + + if ((response.code() / 100) != 2) { + try { + APIException err = + Utils.serializer.fromJson(response.body().charStream(), APIException.class); + if (err.code != null) { + //err.requestId = rid; + err.statusCode = response.code(); + throw err; + } + } catch (IOException ex) { + //throw new JSONException("Unable to read body. " + ex.getMessage(), rid); + throw new JSONException("Unable to read body. "); + } + } + return response; + } + + private String buildCredentials() { + String user = ""; + String pass = ""; + if (hasAccessToken()) { + String[] parts = accessToken.split(":"); + if (parts.length >= 1) { + user = parts[0]; + } + if (parts.length >= 2) { + pass = parts[1]; + } + } + return Credentials.basic(user, pass); + } + + /** + * Overrides {@link Object#hashCode()} + * + * @return the hash code + */ + @Override + public int hashCode() { + int code = this.url.hashCode(); + if (this.hasAccessToken()) { + code = code * 31 + this.accessToken.hashCode(); + } + return code; + } + + /** + * Overrides {@link Object#equals(Object)} + * + * @param o the object to compare + * @return a boolean specifying equality + */ + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (!(o instanceof Client)) return false; + + Client other = (Client) o; + if (!this.url.equalsIgnoreCase(other.url)) { + return false; + } + return Objects.equals(this.accessToken, other.accessToken); + } + + /** + * A builder class for creating client objects + */ + public static class Builder { + + private String url; + + private OkHttpClient baseHttpClient; + private String accessToken; + private CertificatePinner cp; + private KeyManager[] keyManagers; + private TrustManager[] trustManagers; + private long connectTimeout; + private TimeUnit connectTimeoutUnit; + private long readTimeout; + private TimeUnit readTimeoutUnit; + private long writeTimeout; + private TimeUnit writeTimeoutUnit; + private Proxy proxy; + private ConnectionPool pool; + + public Builder() { + this.baseHttpClient = new OkHttpClient(); + this.baseHttpClient.setFollowRedirects(false); + this.setDefaults(); + } + + public Builder(Client client) { + this.baseHttpClient = client.httpClient.clone(); + this.url = client.url; + this.accessToken = client.accessToken; + } + + private void setDefaults() { + this.setReadTimeout(30, TimeUnit.SECONDS); + this.setWriteTimeout(30, TimeUnit.SECONDS); + this.setConnectTimeout(30, TimeUnit.SECONDS); + this.setConnectionPool(50, 2, TimeUnit.MINUTES); + } + + public Builder setUrl(String url) { + this.url = url; + return this; + } + + /** + * Sets the access token for the client + * + * @param accessToken The access token for the Chain Core or HSM + */ + public Builder setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + /** + * Sets the client's certificate and key for TLS client authentication. + * PEM-encoded, RSA private keys adhering to PKCS#1 or PKCS#8 are supported. + * + * @param certStream input stream of PEM-encoded X.509 certificate + * @param keyStream input stream of PEM-encoded private key + */ + public Builder setX509KeyPair(InputStream certStream, InputStream keyStream) + throws ConfigurationException { + try (PEMParser parser = new PEMParser(new InputStreamReader(keyStream))) { + // Extract certs from PEM-encoded input. + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream); + + // Parse the private key from PEM-encoded input. + Object obj = parser.readObject(); + PrivateKeyInfo info; + if (obj instanceof PEMKeyPair) { + // PKCS#1 Private Key found. + PEMKeyPair kp = (PEMKeyPair) obj; + info = kp.getPrivateKeyInfo(); + } else if (obj instanceof PrivateKeyInfo) { + // PKCS#8 Private Key found. + info = (PrivateKeyInfo) obj; + } else { + throw new ConfigurationException("Unsupported private key provided."); + } + + // Create a new key store and input the pair. + KeySpec spec = new PKCS8EncodedKeySpec(info.getEncoded()); + KeyFactory kf = KeyFactory.getInstance("RSA"); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); + keyStore.setCertificateEntry("cert", certificate); + keyStore.setKeyEntry( + "key", + kf.generatePrivate(spec), + DEFAULT_KEYSTORE_PASSWORD, + new X509Certificate[]{certificate}); + + // Use key store to build a key manager. + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); + + this.keyManagers = keyManagerFactory.getKeyManagers(); + return this; + } catch (GeneralSecurityException | IOException ex) { + throw new ConfigurationException("Unable to store X.509 cert/key pair", ex); + } + } + + /** + * Sets the client's certificate and key for TLS client authentication. + * + * @param certPath file path to PEM-encoded X.509 certificate + * @param keyPath file path to PEM-encoded private key + */ + public Builder setX509KeyPair(String certPath, String keyPath) throws ConfigurationException { + try (InputStream certStream = + new ByteArrayInputStream(Files.readAllBytes(Paths.get(certPath))); + InputStream keyStream = + new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyPath)))) { + return setX509KeyPair(certStream, keyStream); + } catch (IOException ex) { + throw new ConfigurationException("Unable to store X509 cert/key pair", ex); + } + } + + /** + * Trusts the given CA certs, and no others. Use this if you are running + * your own CA, or are using a self-signed server certificate. + * + * @param is input stream of the certificates to trust, in PEM format. + */ + public Builder setTrustedCerts(InputStream is) throws ConfigurationException { + try { + // Extract certs from PEM-encoded input. + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + Collection certificates = + certificateFactory.generateCertificates(is); + if (certificates.isEmpty()) { + throw new IllegalArgumentException("expected non-empty set of trusted certificates"); + } + + // Create a new key store and input the cert. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, DEFAULT_KEYSTORE_PASSWORD); + int index = 0; + for (Certificate certificate : certificates) { + String certificateAlias = Integer.toString(index++); + keyStore.setCertificateEntry(certificateAlias, certificate); + } + + // Use key store to build an X509 trust manager. + KeyManagerFactory keyManagerFactory = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, DEFAULT_KEYSTORE_PASSWORD); + TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException( + "Unexpected default trust managers:" + Arrays.toString(trustManagers)); + } + + this.trustManagers = trustManagers; + return this; + } catch (GeneralSecurityException | IOException ex) { + throw new ConfigurationException("Unable to configure trusted CA certs", ex); + } + } + + /** + * Trusts the given CA certs, and no others. Use this if you are running + * your own CA, or are using a self-signed server certificate. + * + * @param path The path of a file containing certificates to trust, in PEM format. + */ + public Builder setTrustedCerts(String path) throws ConfigurationException { + try (InputStream is = new FileInputStream(path)) { + return setTrustedCerts(is); + } catch (IOException ex) { + throw new ConfigurationException("Unable to configure trusted CA certs", ex); + } + } + + /** + * Sets the certificate pinner for the client + * + * @param provider certificate provider + * @param subjPubKeyInfoHash public key hash + */ + public Builder pinCertificate(String provider, String subjPubKeyInfoHash) { + this.cp = new CertificatePinner.Builder().add(provider, subjPubKeyInfoHash).build(); + return this; + } + + /** + * Sets the connect timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setConnectTimeout(long timeout, TimeUnit unit) { + this.connectTimeout = timeout; + this.connectTimeoutUnit = unit; + return this; + } + + /** + * Sets the read timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setReadTimeout(long timeout, TimeUnit unit) { + this.readTimeout = timeout; + this.readTimeoutUnit = unit; + return this; + } + + /** + * Sets the write timeout for the client + * + * @param timeout the number of time units for the default timeout + * @param unit the unit of time + */ + public Builder setWriteTimeout(long timeout, TimeUnit unit) { + this.writeTimeout = timeout; + this.writeTimeoutUnit = unit; + return this; + } + + /** + * Sets the proxy for the client + * + * @param proxy + */ + public Builder setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + /** + * Sets the connection pool for the client + * + * @param maxIdle the maximum number of idle http connections in the pool + * @param timeout the number of time units until an idle http connection in the pool is closed + * @param unit the unit of time + */ + public Builder setConnectionPool(int maxIdle, long timeout, TimeUnit unit) { + this.pool = new ConnectionPool(maxIdle, unit.toMillis(timeout)); + return this; + } + + /** + * Builds a client with all of the provided parameters. + */ + public Client build() throws ConfigurationException { + return new Client(this); + } + } +} diff --git a/src/main/java/io/bytom/http/SuccessMessage.java b/src/main/java/io/bytom/http/SuccessMessage.java new file mode 100644 index 0000000..c069a82 --- /dev/null +++ b/src/main/java/io/bytom/http/SuccessMessage.java @@ -0,0 +1,8 @@ +package io.bytom.http; + +/** + * This class represents RPC success responses whose content is not meaningful. + */ +public class SuccessMessage { + public String message; +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 0000000..5938392 --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,8 @@ +bytom.api.url=http://127.0.0.1:9888 +client.access.token= + +# bytom.api.url=http://10.100.7.47:9888/ +# client.access.token=wt:3d17dbb953cedd53353bf3f342bb2929e9505105ffeb21670e6bd00abeef3772 + +#bytom.api.url=http://127.0.0.1:9888/ +#client.access.token=sheng:49d1623f5991c62a5094e761477ddd2838dceb49c22fbf84b492a54f1df88123 diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..d481a3b --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,20 @@ + log4j.rootLogger=debug, stdout, R + log4j.appender.stdout=org.apache.log4j.ConsoleAppender + log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + + log4j.logger.org.apache.commons.httpclient=info + log4j.logger.httpclient.wire.content=info + log4j.logger.httpclient.wire.header=info + + # Pattern to output the caller's file name and line number. + log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n + + log4j.appender.R=org.apache.log4j.RollingFileAppender + log4j.appender.R.File=bytom.log + log4j.appender.R.MaxFileSize= 100KB + + # Keep one backup file + log4j.appender.R.MaxBackupIndex=1 + + log4j.appender.R.layout=org.apache.log4j.PatternLayout + log4j.appender.R.layout.ConversionPattern=%-4r %-5p [%d{yyyy-MM-dd HH:mm:ss}] %m%n \ No newline at end of file diff --git a/src/test/java/io/bytom/AppTest.java b/src/test/java/io/bytom/AppTest.java new file mode 100644 index 0000000..b5d58a9 --- /dev/null +++ b/src/test/java/io/bytom/AppTest.java @@ -0,0 +1,58 @@ +package io.bytom; + +import com.squareup.okhttp.*; +import org.junit.Test; + +import java.io.IOException; + +/** + * Unit test for simple App. + */ +public class AppTest { + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient client = new OkHttpClient(); + + + @Test + public void testListAccounts() throws IOException { + + String postBody = "{}"; + + Request request = new Request.Builder() + .url("http://127.0.0.1:9888/list-accounts") + .post(RequestBody.create(JSON, postBody)) + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } + + + @Test + public void testCreateAccount() throws IOException { + + String postBody = "{\n" + + " \"root_xpubs\": [\n" + + " \"012454d25928d52d42e3ee1f2bebe0916974d958f9ec08c9a028043ffe3dd95630c1b788c947b8c07ede2a4b5e3e3bbe0e305bab4526a7bc67b21e1d051e74ef\"\n" + + " ], \n" + + " \"quorum\": 1, \n" + + " \"alias\": \"sheng\"\n" + + "}"; + + Request request = new Request.Builder() + .url("http://127.0.0.1:9888/create-account") + .post(RequestBody.create(JSON, postBody)) + .build(); + + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + System.out.println(response.body().string()); + } +} + diff --git a/src/test/java/io/bytom/TestUtils.java b/src/test/java/io/bytom/TestUtils.java new file mode 100644 index 0000000..25f1690 --- /dev/null +++ b/src/test/java/io/bytom/TestUtils.java @@ -0,0 +1,29 @@ +package io.bytom; + +import io.bytom.common.Configuration; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; + +/** + * TestUtils provides a simplified api for testing. + */ +public class TestUtils { + public static Client generateClient_old() throws BytomException { + + String coreURL = "http://127.0.0.1:9888"; + + return new Client(coreURL); + } + + public static Client generateClient() throws BytomException { + + String coreURL = Configuration.getValue("bytom.api.url"); + String accessToken = Configuration.getValue("client.access.token"); + + if (coreURL == null || coreURL.isEmpty()) { + coreURL = "http://127.0.0.1:9888/"; + } + + return new Client(coreURL, accessToken); + } +} diff --git a/src/test/java/io/bytom/integration/AccessTokenTest.java b/src/test/java/io/bytom/integration/AccessTokenTest.java new file mode 100644 index 0000000..49930b9 --- /dev/null +++ b/src/test/java/io/bytom/integration/AccessTokenTest.java @@ -0,0 +1,39 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.AccessToken; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.List; + +public class AccessTokenTest { + + static Client client; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + @Test + public void testTokenCreate() throws Exception { + AccessToken accessToken = new AccessToken.Builder().setId("sheng").create(client); + } + + + @Test + public void testTokenList() throws Exception { + List tokenList = AccessToken.list(client); + } + + @Test + public void testTokenCheck() throws Exception { + String secret = "5e37378eb59de6b10e60f2247ebf71c4955002e75e0cd31ede3bf48813a0a799"; + AccessToken.check(client, "sheng", secret); + } +} diff --git a/src/test/java/io/bytom/integration/AccountTest.java b/src/test/java/io/bytom/integration/AccountTest.java new file mode 100644 index 0000000..081106f --- /dev/null +++ b/src/test/java/io/bytom/integration/AccountTest.java @@ -0,0 +1,98 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Account; +import io.bytom.api.Address; +import io.bytom.api.Receiver; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AccountTest { + + static Client client; + static Account account; + + @Test + public void testAccountCreate() throws Exception { + client = TestUtils.generateClient(); + + String alias = "AccountTest.testAccountCreate.002"; + Integer quorum = 1; + List root_xpubs = new ArrayList<>(); + root_xpubs.add("c4b25825e92cd8623de4fd6a35952ad0efb2ed215fdb1b40754f0ed12eff7827d147d1e8b003601ba2f78a4a84dcc77e93ed282633f2679048c5d5ac5ea10cb5"); + + +// Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); +// key = Key.create(client, builder); + + Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + account = Account.create(client, builder); + + assertNotNull(account.id); + assertEquals(alias.toLowerCase(), account.alias); + } + + @Test + public void testAccountList() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + } + + @Test + public void testAccountDelete() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + //delete the last Account Object + Account.delete(client, alias); + } + + @Test + public void testReceiverCreate() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + String id = accountList.get(accountList.size()-1).id; + + Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); + Receiver receiver = receiverBuilder.create(client); + + assertNotNull(receiver.address); + assertNotNull(receiver.controlProgram); + } + + @Test + public void testAddressList() throws Exception { + client = TestUtils.generateClient(); + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + String id = accountList.get(accountList.size()-1).id; + + Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias); + List

addressList = addressBuilder.list(client); + + assertNotNull(addressList); + } + + @Test + public void testAddressValidate() throws Exception { + client = TestUtils.generateClient(); + + List accountList = Account.list(client); + String alias = accountList.get(accountList.size()-1).alias; + String id = accountList.get(accountList.size()-1).id; + + Account.AddressBuilder addressBuilder = new Account.AddressBuilder().setAccountId(id).setAccountAlias(alias); + List
addressList = addressBuilder.list(client); + + Address address = addressBuilder.validate(client, addressList.get(0).address); + assertEquals(true, address.is_local); + } + +} diff --git a/src/test/java/io/bytom/integration/AssetTest.java b/src/test/java/io/bytom/integration/AssetTest.java new file mode 100644 index 0000000..a35a0a6 --- /dev/null +++ b/src/test/java/io/bytom/integration/AssetTest.java @@ -0,0 +1,74 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Account; +import io.bytom.api.Address; +import io.bytom.api.Asset; +import io.bytom.api.Receiver; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AssetTest { + + static Client client; + static Account account; + static Asset asset; + + @Test + public void testAssetCreate() throws Exception { + client = TestUtils.generateClient(); + + List accountList = Account.list(client); + String alias = "GOLD"; + + List xpubs = accountList.get(0).xpubs; + + Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); + asset = builder.create(client); + assertNotNull(asset); + } + + @Test + public void testAssetGet() throws Exception { + client = TestUtils.generateClient(); + Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); + String id = queryBuilder.list(client).get(1).id; + queryBuilder.setId(id); + Asset asset = queryBuilder.get(client); + } + + @Test + public void testAssetList() throws Exception { + client = TestUtils.generateClient(); + Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); + List assetList = queryBuilder.list(client); + assertEquals(2, assetList.size()); + } + + @Test + public void testUpdateAssetAlias() throws Exception { + client = TestUtils.generateClient(); + + Asset.QueryBuilder queryBuilder = new Asset.QueryBuilder(); + String id = queryBuilder.list(client).get(1).id; + + String alias = "HELLOWORLD"; + + + Asset.AliasUpdateBuilder aliasUpdateBuilder = + new Asset.AliasUpdateBuilder() + .setAlias(alias) + .setAssetId(id); + aliasUpdateBuilder.update(client); + } + +} diff --git a/src/test/java/io/bytom/integration/KeyTest.java b/src/test/java/io/bytom/integration/KeyTest.java new file mode 100644 index 0000000..a51ad8a --- /dev/null +++ b/src/test/java/io/bytom/integration/KeyTest.java @@ -0,0 +1,58 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.Key; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +public class KeyTest { + + static Client client; + static Key key; + + @Test + public void testClientKeyCreate() throws Exception { + client = TestUtils.generateClient(); + + String alias = "KeyTest.testKeyCreate.successli004"; + String password = "123456"; + + Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); + key = Key.create(client, builder); + + assertNotNull(key.xpub); + assertEquals(alias.toLowerCase(), key.alias); + } + + @Test + public void testClientKeyList() throws Exception { + client = TestUtils.generateClient(); + List keyList = Key.list(client); + } + + @Test + public void testClientKeyDelete() throws Exception { + client = TestUtils.generateClient(); + List keyList = Key.list(client); + String xpub = keyList.get(keyList.size()-1).xpub; + //delete the last Key Object + Key.delete(client, xpub, "123456"); + } + + @Test + public void testClientKeyResetPwd() throws BytomException { + client = TestUtils.generateClient(); + List keyList = Key.list(client); + String xpub = keyList.get(keyList.size()-1).xpub; + Key.resetPwd(client, xpub, "123456", "123456789"); + Key.delete(client, xpub, "123456789"); + } + +} diff --git a/src/test/java/io/bytom/integration/TransactionTest.java b/src/test/java/io/bytom/integration/TransactionTest.java new file mode 100644 index 0000000..b92cb84 --- /dev/null +++ b/src/test/java/io/bytom/integration/TransactionTest.java @@ -0,0 +1,269 @@ +package io.bytom.integration; + +import io.bytom.TestUtils; +import io.bytom.api.*; +import io.bytom.exception.BytomException; +import io.bytom.http.Client; +import org.apache.log4j.Logger; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TransactionTest { + + static Client client; + + static Key senderKey; + static Key receiverKey; + static Account senderAccount; + static Account receiverAccount; + static Receiver senderReceiver; + static Receiver receiverReceiver; + static Address senderAddress; + static Address receiverAddress; + static Asset senderAsset; + static Asset receiverAsset; + + static { + try { + client = TestUtils.generateClient(); + } catch (BytomException e) { + e.printStackTrace(); + } + } + + + private static Logger logger = Logger.getLogger(TransactionTest.class); + + @Test + public void testCreateAll() throws Exception { + + testSenderKeyCreate(); + testReceiverKeyCreate(); + + testSenderAccountCreate(); + testReceiverAccountCreate(); + +// testSenderReceiverCreate(); + testReceiverReceiverCreate(); + +// testSenderAssetCreate(); +// testReceiverAssetCreate(); + + } + + @Test + public void testGetAll() throws Exception { + + senderKey = Key.list(client).get(0); + receiverKey = Key.list(client).get(1); + logger.info("senderKey:"+senderKey.toJson()); + logger.info("receiverKey:"+receiverKey.toJson()); + + senderAccount = Account.list(client).get(0); + receiverAccount = Account.list(client).get(1); + logger.info("senderAccount:"+senderAccount.toJson()); + logger.info("receiverAccount:"+receiverAccount.toJson()); + + receiverAddress = new Account.AddressBuilder() + .setAccountAlias(receiverAccount.alias) + .setAccountId(receiverAccount.id) + .list(client).get(0); + logger.info("receiver-address:"+receiverAddress.toJson()); + + senderAsset = new Asset.QueryBuilder().list(client).get(0); + receiverAsset = new Asset.QueryBuilder().list(client).get(1); + logger.info("senderAsset:"+senderAsset.toJson()); + logger.info("receiverAsset:"+receiverAsset.toJson()); + } + + @Test + public void testTransactionAll() throws Exception { + testGetAll(); + + logger.info("before transaction:"); + + List balanceList = new Balance.QueryBuilder().list(client); + + logger.info("transaction:"); + + Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(senderAsset.id) + .setAmount(200000000) + ).build(client); + + Transaction.Template singer = new Transaction.SignerBuilder().sign(client, + controlAddress, "123456"); + + logger.info("singer:"+singer.toJson()); + + Transaction.SubmitResponse txs = Transaction.submit(client, singer); + + logger.info("txs:"+txs.toJson()); + + logger.info("after transaction."); + + balanceList = new Balance.QueryBuilder().list(client); + + } + + @Test + public void testListTransactions() throws Exception { + String tx_id = "b9a1baa1ae391b502fe5543a9559e6d98dc6cf77f9327daaebcb7222c3bebda6"; + List transactionList = + new Transaction.QueryBuilder().setTxId(tx_id).list(client); + for (Transaction tx: transactionList + ) { + if (tx.txId.equalsIgnoreCase(tx_id)) { + logger.info(tx.toJson()); + } + } + } + + public void testSenderKeyCreate() throws Exception { + String alias = "sender-key"; + String password = "123456"; + + Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); + senderKey = Key.create(client, builder); + + logger.info("create-sender-key:"+senderKey.toJson()); + } + + public void testReceiverKeyCreate() throws Exception { + String alias = "receiver-key"; + String password = "123456"; + + Key.Builder builder = new Key.Builder().setAlias(alias).setPassword(password); + receiverKey = Key.create(client, builder); + + logger.info("create-receiver-key:"+receiverKey.toJson()); + } + + public void testSenderAccountCreate() throws Exception { + String alias = "sender-account"; + Integer quorum = 1; + List root_xpubs = new ArrayList(); + root_xpubs.add(senderKey.xpub); + + Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + + logger.info(builder.toString()); + + senderAccount = Account.create(client, builder); + + logger.info("create-sender-account:"+senderAccount.toJson()); + } + + public void testReceiverAccountCreate() throws Exception { + String alias = "receiver-account"; + Integer quorum = 1; + List root_xpubs = new ArrayList<>(); + root_xpubs.add(receiverKey.xpub); + + Account.Builder builder = new Account.Builder().setAlias(alias).setQuorum(quorum).setRootXpub(root_xpubs); + receiverAccount = Account.create(client, builder); + + logger.info("create-receiver-account:"+receiverAccount.toJson()); + } + + public void testSenderReceiverCreate() throws Exception { + String alias = senderAccount.alias; + String id = senderAccount.id; + + Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); + senderReceiver = receiverBuilder.create(client); + + logger.info("create-receiver:"+senderReceiver.toJson()); + logger.info("receiver-address:"+senderReceiver.address); + } + + public void testReceiverReceiverCreate() throws Exception { + String alias = receiverAccount.alias; + String id = receiverAccount.id; + + Account.ReceiverBuilder receiverBuilder = new Account.ReceiverBuilder().setAccountAlias(alias).setAccountId(id); + receiverReceiver = receiverBuilder.create(client); + + logger.info("create-receiver:"+receiverReceiver.toJson()); + logger.info("receiver-address:"+receiverReceiver.address); + } + + public void testSenderAssetCreate() throws Exception { + String alias = "sender-asset"; + + List xpubs = senderAccount.xpubs; + + Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); + senderAsset = builder.create(client); + + logger.info("create-sender-asset:"+senderAsset.toJson()); + } + + public void testReceiverAssetCreate() throws Exception { + String alias = "receiver-asset"; + + List xpubs = receiverAccount.xpubs; + + Asset.Builder builder = new Asset.Builder() + .setAlias(alias) + .setQuorum(1) + .setRootXpubs(xpubs); + receiverAsset = builder.create(client); + + logger.info("create-receiver-asset:"+receiverAsset.toJson()); + } + + /** + * build + sign + submit transaction + * + * @throws BytomException + */ + public void testBasicTransaction() throws BytomException { + logger.info("before transaction:"); + + List balanceList = new Balance.QueryBuilder().list(client); + + logger.info(balanceList.get(0).toJson()); + logger.info(balanceList.get(1).toJson()); + + Transaction.Template controlAddress = new Transaction.Builder() + .addAction( + new Transaction.Action.SpendFromAccount() + .setAccountId(senderAccount.id) + .setAssetId(senderAsset.id) + .setAmount(300000000) + ) + .addAction( + new Transaction.Action.ControlWithAddress() + .setAddress(receiverAddress.address) + .setAssetId(receiverAsset.id) + .setAmount(200000000) + ).build(client); + + logger.info("after transaction."); + + balanceList = new Balance.QueryBuilder().list(client); + + logger.info(balanceList.get(0).toJson()); + logger.info(balanceList.get(1).toJson()); + + } + +}