提交 e47a6961 编写于 作者: X Xiao Li 提交者: bors-libra

[json-rpc] added client implementation guide

Closes: #5532
上级 7df9649a
......@@ -26,6 +26,7 @@ To begin contributing, [sign the CLA](https://libra.org/en-US/cla-sign/). You ca
* [Welcome](https://developers.libra.org/docs/welcome-to-libra)
* [Libra Protocol: Key Concepts](https://developers.libra.org/docs/libra-protocol)
* [Life of a Transaction](https://developers.libra.org/docs/life-of-a-transaction)
* [JSON-RPC SPEC](json-rpc/json-rpc-spec.md)
### Try Libra Core
* [My First Transaction](https://developers.libra.org/docs/my-first-transaction)
......
# Basics
- [ ] module structure:
- libra
- LibraClient: high level APIs interface, should support application to do easy mock / stub development.
- jsonrpc: jsonrpc client interface, include plain data classes / structs defined in Libra JSON-RPC SPEC document.
- types: data transfer object types for jsonrpc client, should match server side JSON-RPC spec data types.
- stdlib: move stdlib script utils.
- testnet: testnet utils, should include FaucetService for handing testnet mint.
- types: Libra onchain data structure types.
- utils: includes crypto, data types converting and other utils functions
- signing, sha3 hashing, address parsing and converting, hex encoding / decoding
- LCS utils
- [ ] JSON-RPC 2.0 Spec:
- spec version validation.
- batch requests and responses handling.
- [ ] JSON-RPC client error handling should distinguish the following 3 type errors:
- Transport layer error, e.g. HTTP call failure.
- JSON-RPC protocol error: e.g. server respond non json data, or can't be parsed into [Libra JSON-RPC SPEC][1] defined data structure, or missing result & error field.
- JSON-RPC error: error returned from server.
- [ ] https
- [ ] Client connection pool: keep connection alive for less likely getting inconsistent data from connecting to multiple servers.
- [ ] Handle stale responses:
- [ ] client tracks latest server respond block version and timestamp, raise error when received server response contains stale version / timestamp.
- [ ] parse and use libra_chain_id, libra_ledger_version and libra_ledger_tiemstamp in the JSONRPC response.
- [ ] Parsing and gen Libra Account Identifier (see [LIP-5][2])
- bech32 addresses/subaddresses support
- [ ] language specific standard release publish: e.g. java maven central repo, python pip
- [ ] Multi-network: initialize Client with chain id, JSON-RPC server URL
- [ ] Handle unsigned int64 data type properly
- [ ] Validate server chain id: client should be initialized with chain id and validate server respond chain id is same.
- [ ] Validate input parameters, e.g. invalid account address: "kkk". Should return / raise InvalidArgumentError.
# High Level API
- [ ] transfer: wrap peer to peer transfer with metadata script and submit transaction
- may have option to wait until transaction executed successfully or failed.
- [ ] waitForTransactionExecuted(String accountAddress, long sequence, String signedTranscationHash, long timeout):
- for given signed transaction sender address, sequence number, expiration time (or 5 sec timeout) to wait and validate execution result is executed, otherwise return/raise an error / flag to tell it is not executed.
- when signedTransactionHash validation failed, it should return / raise TransactionSequenceNumberConflictError
- when transaction execution vm_status is not "executed", it should return / raise TransactionExecutionFailure
# Read from Blockchain
- [ ] Get metadata
- [ ] Get currencies
- [ ] Get events
- [ ] Get transactions
- [ ] Get account
- [ ] Get account transaction
- [ ] Get account transactions
- [ ] Get account events
- [ ] Handle error response
- [ ] Serialize result JSON to typed data structure
# Submit Transaction
- [ ] Submit [p2p transfer][3] transaction
- [ ] Submit other [Move Stdlib scripts][4]
- [ ] Wait for transaction executed:
- wait for a transaction by get_transaction by account and transaction sequence, no validation of vm_status and signature. (low level API, consider not expose, only for internal or test usage.)
# Testnet support
- [ ] Generate ed25519 private key, derive ed25519 public keys from private key. generate Single and MultiSig auth-keys
- [ ] Mint coins through Faucet service
See [doc][5] for above concepts.
# Examples
- [ ] Query blockchain example
- [ ] Submit p2p transfer transaction example
# Nice to have
- [ ] Async client
- [ ] CLI connects to testnet for trying out features.
[1]: https://github.com/libra/libra/blob/master/json-rpc/json-rpc-spec.md "Libra JSON-RPC SPEC"
[2]: https://github.com/libra/lip/blob/master/lips/lip-5.md "LIP-5"
[3]: https://github.com/libra/libra/blob/master/language/stdlib/transaction_scripts/doc/peer_to_peer_with_metadata.md "P2P Transafer"
[4]: https://github.com/libra/libra/tree/master/language/stdlib/transaction_scripts/doc "Move Stdlib scripts"
[5]: https://github.com/libra/libra/blob/master/client/libra-dev/README.md "Libra Client Dev Doc"
## JSON-RPC Client Implementation Guide
### Overview
To implement a client connecting to Libra JSON-RPC APIs, you need consider the followings:
* [JSON-RPC client](#json-rpc-client): talks to Libra JSON-RPC server.
* [Testnet](#testnet): connect to testnet to do integration test and confirm your client works as expected.
* [Query Blockchain](#query-blockchain): read data from Libra blockchain.
* [Submit Transaction](#submit-transaction): write data to Libra blockchain.
* [Error handling](#error-handling): handle errors.
### JSON-RPC client
Any JSON-RPC 2.0 client should be able to work Libra JSON-RPC APIs.
Libra JSON-RPC APIs extend to JSON-RPC 2.0 Spec for specific use case, check [Libra Extensions](./../json-rpc-spec.md#libra-extensions) for details, we will discuss more about them in [Error Handling](#error-handling) section.
### Testnet
A simplest way to validate your client works is connecting it to Testnet(https://client.testnet.libra.org/v1).
For some query blockchain methods like [get_currencies](method_get_currencies.md) or [get_metadata](method_get_metadata.md), you don't need anything else other than a HTTP client to get back response from server.
Try out [get_currencies exmpale](method_get_currencies.md#example) on Testnet, and this can be the first query blockchain API you implement for your client.
When you need test [submit](method_submit.md) transaction, like peer to peer transfer coins, you will need accounts created for both sender and receiver. Technically it is a transaction (with creating account script) need to be submitted and executed, but creating account transaction is permitted to special accounts, and Testnet does not publish these accounts' private key, thus you can't do it by you own.
Instead, we created a service named `Faucet` for anyone want to create account and mint coins on Testnet.
Please follow [Testnet Faucet Service](service_testnet_faucet.md) to implement mint coins for testing accounts, then you are ready to test submit a peer to peer transfer transaction.
### Query Blockchain
All the methods prefixed with `get_` listed at [here](./../json-rpc-spec.md#overview) are designed for querying Libra blockchain data.
You may start with implementing [get_currencies](method_get_currencies.md), which is simplest API that does not require any arguments and always respond same result for Testnet.
When you implement [get_account](method_get_account.md), you can use the following 3 static account address to test the API against with Testnet:
| account name | address hex-encoded string | description |
|-------------------------|----------------------------------|----------------------------------------------------------------------------------------------------------------------------|
| root account address | 0000000000000000000000000A550C18 | A special root account, stores important global resource information like all currencies info |
| core code address | 00000000000000000000000000000001 | A special code account, we will need it for submit transaction, it stores currency code type info |
| designed dealer address | 000000000000000000000000000000DD | A special account for minting coins on Testnet, checkout [Testnet Faucet Service](service_testnet_faucet.md) for more info |
As above account addresses are static on Testnet, it is convenient for you to test against them for [get_account](method_get_account.md) method.
However, if you implemented [Testnet Faucet Service](service_testnet_faucet.md), you can create your own testing account for testing on Testnet.
Similarly, we can test our [get_account_transaction](method_get_account_transaction.md) implementation with `root account address`.
> We need call [get_account](method_get_account.md) and [get_account_transaction](method_get_account_transaction.md) when we implement and test [Submit Transaction](#submit_transaction) method.
> So you should at least implement and comfirm these two methods are working as expected.
### Submit Transaction
To implement submitting a transaction, you may follow the following steps:
1. [Create local account](#create-local-account): it includes an address, [Ed25519](https://ed25519.cr.yp.to/) genrated private key and public key, an authentication key that is generated from public key.
2. [Create and sign transaction](#create-and-sign-transaction)
3. [Submit transaction](#submit-transaction)
4. [Wait for transaction executed and validate result](#wait-for-transaction-executed-and-validate-result): the execution can fail after you submitted successfully.
The following diagram shows the sequence of submit and wait for a peer to peer transaction exeucted successfully:
![Submit and wait for transaction executed successfully](images/submit_wait_transaction.png)
#### Create local account
A local account holds secrets of onchain account: the private key.
Maintaining the local account or keep secret of private key is out of a Libra client's scope. In this guide, we use [Libra Swiss Knife][6] to generate local account keys:
``` shell
# generate test keypair
cargo run -p swiss-knife -- generate-test-ed25519-keypair
{
"error_message": "",
"data": {
"libra_account_address": "a74fd7c46952c497e75afb0a7932586d",
"libra_auth_key": "459c77a38803bd53f3adee52703810e3a74fd7c46952c497e75afb0a7932586d",
"private_key": "cd9a2c90296a210249128ae3c908611637b2e00efd4986670e252abf3fabd1a9",
"public_key": "447fc3be296803c2303951c7816624c7566730a5cc6860a4a1bd3c04731569f5"
}
}
```
> To run this by yourself, clone https://github.com/libra/libra.git, and run `./scripts/dev_setup.sh` to setup dev env.
> You can run command in above example at root directory of libra codebase.
#### Create and sign transaction
Now we have local account address and keys, we can start to prepare a transaction.
In this guide we use peer to peer transfer as example, others will be similar except some script can only be submitted by specific account.
There are several development tools available for you:
1. [Transaction Builder Generator][2]: this is actively in development, current supports C++, Java, Python and Rust ([latest language supports][10]).
2. swiss-knife: check out [Swiss Knife generate raw transaction and sign transaction][8]; when we don't have transaction builder generator in the language you want to develop the client, you can wrap the swiss-knife [release binary][9] for creating and signing transaction.
3. [C-binding][11]: if you had experience with c-binding, this may not a bad choice :)
Here we give an example how to create and sign transaction with option 1 in Java. Please follow the guide at [Transaction Builder Generator][12] to generate code into your project.
**Example**: create and sign a transaction that transfers 12 LBR coins from account1 to account2.
```Java
ChainId testNetChainID = new ChainId((byte) 2); // Testnet chain id is static value
String currencyCode = "LBR";
String account1_address = "a74fd7c46952c497e75afb0a7932586d";
String account1_public_key = "447fc3be296803c2303951c7816624c7566730a5cc6860a4a1bd3c04731569f5";
String account1_private_key = "cd9a2c90296a210249128ae3c908611637b2e00efd4986670e252abf3fabd1a9";
String account2_address = "5b9f7691937732eedfbe4f194275247b";
long amountToTransfer = coins(12);
// step 1: create transaction script:
TypeTag currencyCodeMoveResource = new TypeTag.Struct(new StructTag(
bytesToAddress(hexToBytes("00000000000000000000000000000001")), // 0x1 is core code account address
new Identifier(currencyCode),
new Identifier(currencyCode),
new ArrayList<>()
));
Script script = Helpers.encode_peer_to_peer_with_metadata_script( // Helpers.encode_xxx is code generated by transaction builder generator
currencyCodeMoveResource,
hexToAddress(account2_address),
amountToTransfer,
new Bytes(new byte[]{}),
new Bytes(new byte[]{})
);
// step 2: get current submitting transaction account sequence number.
Account account1Data = client.getAccount(account1_address);
// step 3: create RawTransaction
RawTransaction rt = new RawTransaction(
hexToAddress(account1_address),
account1Data.sequence_number,
new TransactionPayload.Script(script),
coins(1), // maxGasAmount
0L, // gasUnitPrice, you can always set gas unit price to zero on Testnet. At launch, gas unit price can be zero in most of time. Only during high congestion, you may specify a gas price.
currencyCode,
System.currentTimeMillis()/1000 + 30, // expirationTimestampSecs, expire after 30 seconds
testNetChainID
);
byte[] rawTxnBytes = toLCS(rt);
```
You can find imports and util functions code [here](#util-functions).
The following code does signing transation:
```Java
// sha3 hash "LIBRA::RawTransaction" bytes first, then concat with raw transaction bytes to create message for signing.
byte[] hash = concat(sha3Hash("LIBRA::RawTransaction".getBytes()), rawTxnBytes);
// [bouncycastle](https://www.bouncycastle.org/)'s Ed25519Signer
Ed25519Signer signer = new Ed25519Signer();
byte[] privateKeyBytes = hexToBytes(account1_private_key);
signer.init(true, new Ed25519PrivateKeyParameters(privateKeyBytes, 0));
signer.update(hash, 0, hash.length);
byte[] sign = signer.generateSignature();
SignedTransaction st = new SignedTransaction(rt, new TransactionAuthenticator.Ed25519(
new Ed25519PublicKey(new Bytes(hexToBytes(account1_public_key))),
new Ed25519Signature(new Bytes(sign))
));
String signedTxnData = bytesToHex(toLCS(st));
```
For more details related to Libra croypto, please checkout [Croypto Spec](../../specifications/crypto/spec.md).
When you implement above logic, you may extract `createRawTransaction` and `createSignedTransaction` methods and use the following data to confirm their logic is correct:
1. Given the account sequence number: 0.
2. Given expirationTimestampSecs to 1997844332.
3. Keep other data same, you should get:
1. hex-encoded raw transaction LCS serialized bytes: A74FD7C46952C497E75AFB0A7932586D000000000000000001E101A11CEB0B010000000701000202020403061004160205181D0735610896011000000001010000020001000003020301010004010300010501060C0108000506080005030A020A020005060C05030A020A020109000C4C696272614163636F756E741257697468647261774361706162696C6974791B657874726163745F77697468647261775F6361706162696C697479087061795F66726F6D1B726573746F72655F77697468647261775F6361706162696C69747900000000000000000000000000000001010104010C0B0011000C050E050A010A020B030B0438000B05110202010700000000000000000000000000000001034C4252034C42520004035B9F7691937732EEDFBE4F194275247B01001BB700000000000400040040420F00000000000000000000000000034C42526CAF14770000000002
2. hex-encoded signed transaction LCS serialized bytes: A74FD7C46952C497E75AFB0A7932586D000000000000000001E101A11CEB0B010000000701000202020403061004160205181D0735610896011000000001010000020001000003020301010004010300010501060C0108000506080005030A020A020005060C05030A020A020109000C4C696272614163636F756E741257697468647261774361706162696C6974791B657874726163745F77697468647261775F6361706162696C697479087061795F66726F6D1B726573746F72655F77697468647261775F6361706162696C69747900000000000000000000000000000001010104010C0B0011000C050E050A010A020B030B0438000B05110202010700000000000000000000000000000001034C4252034C42520004035B9F7691937732EEDFBE4F194275247B01001BB700000000000400040040420F00000000000000000000000000034C42526CAF147700000000020020447FC3BE296803C2303951C7816624C7566730A5CC6860A4A1BD3C04731569F5400A40B32AA9EB1C16F48E78F01AE3EE90AEDFABF259F0E74058639E46F082FF475FD24A93DE4BD9D701ACB8E31660BCB301065216D41A0DBAA8DB5CC63A528D05
#### Util Functions
```Java
import com.facebook.lcs.LcsSerializer;
import com.facebook.serde.Bytes;
import com.facebook.serde.Serializer;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.libra.stdlib.Helpers;
import org.libra.types.*;
import java.io.IOException;
import java.util.ArrayList;
// ......
public static long coins(long n) {
return n * 1000000;
}
public static AccountAddress hexToAddress(String hex) {
return bytesToAddress(hexToBytes(hex));
}
static AccountAddress bytesToAddress(byte[] values) {
assert values.length == 16;
Byte[] address = new Byte[16];
for (int i = 0; i < 16; i++) {
address[i] = Byte.valueOf(values[i]);
}
return new AccountAddress(address);
}
public static byte[] hexToBytes(String hex) {
return BaseEncoding.base16().decode(hex.toUpperCase());
}
public static String bytesToHex(byte[] bytes) {
return BaseEncoding.base16().encode(bytes);
}
public static String bytesToHex(Bytes bytes) {
return bytesToHex(bytes.content());
}
public static byte[] toLCS(RawTransaction rt) throws Exception {
Serializer serializer = new LcsSerializer();
rt.serialize(serializer);
return serializer.get_bytes();
}
public static byte[] toLCS(SignedTransaction rt) throws Exception {
Serializer serializer = new LcsSerializer();
rt.serialize(serializer);
return serializer.get_bytes();
}
public static byte[] sha3Hash(byte[] data) {
SHA3.DigestSHA3 digestSHA3 = new SHA3.Digest256();
return digestSHA3.digest(data);
}
public static byte[] concat(byte[] part1, byte[] part2) {
byte[] ret = new byte[part1.length + part2.length];
System.arraycopy(part1, 0, ret, 0, part1.length);
System.arraycopy(part2, 0, ret, part1.length, part2.length);
return ret;
}
public static String addressToHex(AccountAddress address) {
byte[] bytes = new byte[16];
for (int i = 0; i < 16; i++) {
bytes[i] = Byte.valueOf(address.value[i]);
}
return bytesToHex(bytes);
}
```
#### Submit transaction
After extracting out creating and signing transaction logic, the JSON-RPC [submit](method_submit.md) method API itself is simple with hex-encoded signed transaction serialized string.
Assume we have the API implemented by `client` like other `get` methods, we call `submit` with the `signedTxnData` to send transaction to server.
```Java
client.submit(signedTxnData);
```
#### Wait for transaction executed and validate result
After transaction is submitted successfully, we need wait for it executed and validate the execution result.
We can call [get_account_transaction](method_get_account_transaction.md) to find the transaction by account address and sequence number.
If transaction has not been executed yet, server responses null:
```Java
private Transaction waitForTransaction(String address, long sequence, boolean includeEvents, long timeoutMillis) throws Exception {
for (long millis = 0, step = 100; millis < timeoutMillis; millis += step) {
Transaction transaction = this.getAccountTransaction(address, sequence, includeEvents);
if (transaction != null) {
return transaction;
}
Thread.sleep(step);
}
return null;
}
```
After the transaction shows up, we should do the following 2 validations:
1. transaction#signature should be same with the signature we created for `SignedTransaction`. This makes sure the transaction we got is the one we submitted.
2. transaction#vm_status#type should be "executed". Type "executed" means the transaction is executed successfully, all other types are failures. See [VMStatus](type_transaction.md#type-vmstatus) doc for more details.
### Error Handling
There are four general types errors you need consider:
- Transport layer error, e.g. HTTP call failure.
- JSON-RPC protocol error: e.g. server respond non json data, or can't be parsed into [Libra JSON-RPC SPEC](./../json-rpc-spec.md) defined data structure, or missing result & error field.
- JSON-RPC error: error returned from server.
- Invalid arguments error: the caller of your client API may provide invalid arguments like invalid hex-encoded account address.
Distinguish above four types errors can help application developer to decide what to do with each different type error:
- Application may consider retry for transport layer error.
- JSON-RPC protocol error indicates a server side bug.
- Invalid arguments error indicates application code bug.
- JSON-RPC error has 2 major groups errors:
- Invalid request: it indicates client side error, either it's application code bug or the client (your code) bug. If you did well with handling invalid arguments, then it means your client code has bug.
- Server error: this can be server side bug, or important information related to submitted transaction validation or execution error.
Other than general error handling, another type error client / application should pay attention is server side stale response. This type problem happens when a Full Node is out of sync with Libra network, or you connected to a sync delayed Full Node in a cluster of Full Nodes. To prevent these problems, we need:
- Track server side data freshness, Libra JSON-RPC server will always respond `libra_ledger_version` and `libra_ledger_timestampusec` (see [Libra Extensions](./../json-rpc-spec.md#libra-extensions)) for client to validate and track server side data freshness.
- Use http connection pool to keep connection alive and reuse, so that it's likely we can hit same Full Node server and less likely to get in-consistent data because of hitting different Full Node in a cluster.
### More
Once you have above basic function works, you have a mininmum client ready for usage.
To make a production quality client, please checkout our [Client CHECKLIST](client_checklist.md).
[1]: https://libra.github.io/libra/libra_types/transaction/struct.SignedTransaction.html "SignedTransaction"
[2]: ./../../language/transaction-builder/generator/README.md "Transaction Builder Generator"
[3]: ./../../client/swiss-knife/README.md "Libra Swiss Knife"
[4]: https://libra.github.io/libra/libra_types/transaction/struct.RawTransaction.html "RawTransaction"
[5]: https://libra.github.io/libra/libra_canonical_serialization/index.html "LCS"
[6]: ./../../client/swiss-knife#generate-a-ed25519-keypair "Swiss Knife Gen Keys"
[7]: ./../../language/stdlib/transaction_scripts/doc/peer_to_peer_with_metadata.md#function-peer_to_peer_with_metadata-1 "P2P script doc"
[8]: ./../../client/swiss-knife#examples-for-generate-raw-txn-and-generate-signed-txn-operations "Swiss Knife gen txn"
[9]: ./../../client/swiss-knife#building-the-binary-in-a-release-optimized-mode "Swiss Knife binary"
[10]: ./../../language/transaction-builder/generator/README.md#supported-languages "Transaction Builder Generator supports"
[11]: ./../../client/libra-dev/include/data.h "C binding head file"
[12]: ./../../language/transaction-builder/generator/README.md#java "Generate Java Txn Builder"
## Testnet Faucet Service
Faucet service is a simple proxy server to mint coins for your test account on Testnet.
As a side effect, it is also the only way you can create an onchain account on Testnet.
> If you wonder how simple it is, check [server code](./../../docker/mint/server.py).
It's interface is very simple, fire a HTTP POST request to `http://faucet.testnet.libra.org/` with the following parameters:
| param name | type | description |
|---------------|--------|---------------------------------|
| amount | int | amount of coins to mint |
| auth_key | string | your account authentication key |
| currency_code | string | the currency code, e.g. LBR |
Server will start a sub-process to sbumit a mint coins transaction to Testnet, and return next new account sequence for account `000000000000000000000000000000DD`.
For example, you can have something like the followings:
```Java
private static String SERVER_URL = "http://faucet.testnet.libra.org/";
public static long mintCoinsAsync(long amount, String authKey, String currencyCode) {
HttpClient httpClient = HttpClient.newHttpClient();
URI uri = URI.create(SERVER_URL + "?amount=" + amount + "&auth_key=" + authKey + "&currency_code=" + currencyCode);
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(uri)
.POST(HttpRequest.BodyPublishers.noBody())
.build();
int retry = 3;
for (int i = 0; i <= retry; i++) {
try {
HttpResponse<String> resp = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() != 200) {
if (i < retry) {
waitAWhile();
continue;
}
throw new RuntimeException(resp.toString());
}
return Long.parseLong(resp.body());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
throw new RuntimeException();
}
private static void waitAWhile() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
```
As the server side code is simple and can only handle 1 request per second, you may face some errors, hence above Java code has simple retry logic.
It is an async process, hence when client received response, the coins are not in the account yet, client need to wait for the transaction executed.
The next new account sequence is provided for you to use call [get_account](method_get_account.md) and wait for the new sequence shows up.
If you tried to call [get_account_transaction](method_get_account_transaction.md) to get the mint transaction, you should wait for the `respond account sequence - 1`.
For example, the following code calls to [get_account_transaction](method_get_account_transaction.md) to wait for minting coins transaction executed.
```Java
private static final long DEFAULT_TIMEOUT = 10 * 1000;
private static String DD_ADDRESS = "000000000000000000000000000000DD";
public static void mintCoins(Client client, long amount, String authKey, String currencyCode) {
long nextAccountSeq = mintCoinsAsync(amount, authKey, currencyCode);
Transaction txn = null;
try {
txn = client.waitForTransaction(DD_ADDRESS, nextAccountSeq - 1, false, DEFAULT_TIMEOUT);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (txn == null) {
throw new RuntimeException("mint coins transaction does not exist / failed, sequence: "+nextAccountSeq);
}
if (!txn.isExecuted()) {
throw new RuntimeException("mint coins transaction failed: " + txn.toString());
}
}
```
......@@ -4,7 +4,7 @@
The Libra client API is based on the JSON-RPC protocol. This specification defines the client API endpoints and types, and provides usage examples.
List of released stable methods:
List of released stable methods (unless specifically mentioned, all parameters are required for the method.):
* [submit](docs/method_submit.md)(data: string) -> void
* [get_transactions](docs/method_get_transactions.md)(start_version: unsigned_int64, limit: unsigned_int64, include_events: boolean) -> List<[Transaction](docs/type_transaction.md)>
......@@ -15,13 +15,15 @@ List of released stable methods:
* [get_events](docs/method_get_events.md)(key: string, start: unsigned_int64, limit: unsigned_int64) -> List<[Event](docs/type_event.md)>
* [get_currencies](docs/method_get_currencies.md)() -> List<[CurrencyInfo](docs/type_currency_info.md)>
Note: unless specifically mentioned, all parameters are required for the method.
> For implementing a client, please checkout our [Client Implementation Guide](docs/client_implementation_guide.md)
## JSON-RPC specification
JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. Refer to the [JSON-RPC Specification](https://www.jsonrpc.org/specification) for further details.
### Libra extensions
### Libra Extensions
JSON-RPC response object is extended with the following fields:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册