Merge pull request #746 from srmo/701-add-dev-docs-in-java
701 add dev docs in java
This commit is contained in:
commit
4c1f1e4e57
|
@ -0,0 +1,17 @@
|
||||||
|
.toggle {
|
||||||
|
padding-bottom: 1em ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle .header {
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle .header:after {
|
||||||
|
content: " ▼";
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle .header.open:after {
|
||||||
|
content: " ▲";
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
let makeCodeBlocksCollapsible = function() {
|
||||||
|
$(".toggle > *").hide();
|
||||||
|
$(".toggle .header").show();
|
||||||
|
$(".toggle .header").click(function() {
|
||||||
|
$(this).parent().children().not(".header").toggle({"duration": 400});
|
||||||
|
$(this).parent().children(".header").toggleClass("open");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// we could use the }(); way if we would have access to jQuery in HEAD, i.e. we would need to force the theme
|
||||||
|
// to load jQuery before our custom scripts
|
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "!layout.html" %}
|
||||||
|
|
||||||
|
{% set css_files = css_files + ["_static/custom_collapsible_code.css"] %}
|
||||||
|
|
||||||
|
# sadly, I didn't find a css style way to add custom JS to a list that is automagically added to head like CSS (above) #}
|
||||||
|
{% block extrahead %}
|
||||||
|
<script type="text/javascript" src="_static/custom_collapsible_code.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
// using this approach as we don't have access to the jQuery selectors
|
||||||
|
// when executing the function on load in HEAD
|
||||||
|
makeCodeBlocksCollapsible();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -194,11 +194,37 @@ through all transactions in the mempool, removing any that were included
|
||||||
in the block, and re-run the rest using CheckTx against the post-Commit
|
in the block, and re-run the rest using CheckTx against the post-Commit
|
||||||
mempool state.
|
mempool state.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
func (app *DummyApplication) CheckTx(tx []byte) types.Result {
|
.. container:: header
|
||||||
return types.OK
|
|
||||||
}
|
**Show/Hide Go Example**
|
||||||
|
|
||||||
|
.. code-block:: go
|
||||||
|
|
||||||
|
func (app *DummyApplication) CheckTx(tx []byte) types.Result {
|
||||||
|
return types.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
ResponseCheckTx requestCheckTx(RequestCheckTx req) {
|
||||||
|
byte[] transaction = req.getTx().toByteArray();
|
||||||
|
|
||||||
|
// validate transaction
|
||||||
|
|
||||||
|
if (notValid) {
|
||||||
|
return ResponseCheckTx.newBuilder().setCode(CodeType.BadNonce).setLog("invalid tx").build();
|
||||||
|
} else {
|
||||||
|
return ResponseCheckTx.newBuilder().setCode(CodeType.OK).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Consensus Connection
|
Consensus Connection
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -228,18 +254,48 @@ The block header will be updated (TODO) to include some commitment to
|
||||||
the results of DeliverTx, be it a bitarray of non-OK transactions, or a
|
the results of DeliverTx, be it a bitarray of non-OK transactions, or a
|
||||||
merkle root of the data returned by the DeliverTx requests, or both.
|
merkle root of the data returned by the DeliverTx requests, or both.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
// tx is either "key=value" or just arbitrary bytes
|
.. container:: header
|
||||||
func (app *DummyApplication) DeliverTx(tx []byte) types.Result {
|
|
||||||
parts := strings.Split(string(tx), "=")
|
**Show/Hide Go Example**
|
||||||
if len(parts) == 2 {
|
|
||||||
app.state.Set([]byte(parts[0]), []byte(parts[1]))
|
.. code-block:: go
|
||||||
} else {
|
|
||||||
app.state.Set(tx, tx)
|
// tx is either "key=value" or just arbitrary bytes
|
||||||
}
|
func (app *DummyApplication) DeliverTx(tx []byte) types.Result {
|
||||||
return types.OK
|
parts := strings.Split(string(tx), "=")
|
||||||
}
|
if len(parts) == 2 {
|
||||||
|
app.state.Set([]byte(parts[0]), []byte(parts[1]))
|
||||||
|
} else {
|
||||||
|
app.state.Set(tx, tx)
|
||||||
|
}
|
||||||
|
return types.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using Protobuf types from the protoc compiler, we always start with a byte[]
|
||||||
|
*/
|
||||||
|
ResponseDeliverTx deliverTx(RequestDeliverTx request) {
|
||||||
|
byte[] transaction = request.getTx().toByteArray();
|
||||||
|
|
||||||
|
// validate your transaction
|
||||||
|
|
||||||
|
if (notValid) {
|
||||||
|
return ResponseDeliverTx.newBuilder().setCode(CodeType.BadNonce).setLog("transaction was invalid").build();
|
||||||
|
} else {
|
||||||
|
ResponseDeliverTx.newBuilder().setCode(CodeType.OK).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Commit
|
Commit
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
@ -263,12 +319,35 @@ It is expected that the app will persist state to disk on Commit. The
|
||||||
option to have all transactions replayed from some previous block is the
|
option to have all transactions replayed from some previous block is the
|
||||||
job of the `Handshake <#handshake>`__.
|
job of the `Handshake <#handshake>`__.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
func (app *DummyApplication) Commit() types.Result {
|
.. container:: header
|
||||||
hash := app.state.Hash()
|
|
||||||
return types.NewResultOK(hash, "")
|
**Show/Hide Go Example**
|
||||||
}
|
|
||||||
|
.. code-block:: go
|
||||||
|
|
||||||
|
func (app *DummyApplication) Commit() types.Result {
|
||||||
|
hash := app.state.Hash()
|
||||||
|
return types.NewResultOK(hash, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
ResponseCommit requestCommit(RequestCommit requestCommit) {
|
||||||
|
|
||||||
|
// update the internal app-state
|
||||||
|
byte[] newAppState = calculateAppState();
|
||||||
|
|
||||||
|
// and return it to the node
|
||||||
|
return ResponseCommit.newBuilder().setCode(CodeType.OK).setData(ByteString.copyFrom(newAppState)).build();
|
||||||
|
}
|
||||||
|
|
||||||
BeginBlock
|
BeginBlock
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
@ -281,16 +360,45 @@ The app should remember the latest height and header (ie. from which it
|
||||||
has run a successful Commit) so that it can tell Tendermint where to
|
has run a successful Commit) so that it can tell Tendermint where to
|
||||||
pick up from when it restarts. See information on the Handshake, below.
|
pick up from when it restarts. See information on the Handshake, below.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
// Track the block hash and header information
|
.. container:: header
|
||||||
func (app *PersistentDummyApplication) BeginBlock(params types.RequestBeginBlock) {
|
|
||||||
// update latest block info
|
|
||||||
app.blockHeader = params.Header
|
|
||||||
|
|
||||||
// reset valset changes
|
**Show/Hide Go Example**
|
||||||
app.changes = make([]*types.Validator, 0)
|
|
||||||
}
|
.. code-block:: go
|
||||||
|
|
||||||
|
// Track the block hash and header information
|
||||||
|
func (app *PersistentDummyApplication) BeginBlock(params types.RequestBeginBlock) {
|
||||||
|
// update latest block info
|
||||||
|
app.blockHeader = params.Header
|
||||||
|
|
||||||
|
// reset valset changes
|
||||||
|
app.changes = make([]*types.Validator, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
/*
|
||||||
|
* all types come from protobuf definition
|
||||||
|
*/
|
||||||
|
ResponseBeginBlock requestBeginBlock(RequestBeginBlock req) {
|
||||||
|
|
||||||
|
Header header = req.getHeader();
|
||||||
|
byte[] prevAppHash = header.getAppHash().toByteArray();
|
||||||
|
long prevHeight = header.getHeight();
|
||||||
|
long numTxs = header.getNumTxs();
|
||||||
|
|
||||||
|
// run your pre-block logic. Maybe prepare a state snapshot, message components, etc
|
||||||
|
|
||||||
|
return ResponseBeginBlock.newBuilder().build();
|
||||||
|
}
|
||||||
|
|
||||||
EndBlock
|
EndBlock
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
@ -304,12 +412,39 @@ EndBlock response. To remove one, include it in the list with a
|
||||||
validator set. Note validator set changes are only available in v0.8.0
|
validator set. Note validator set changes are only available in v0.8.0
|
||||||
and up.
|
and up.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
// Update the validator set
|
.. container:: header
|
||||||
func (app *PersistentDummyApplication) EndBlock(height uint64) (resEndBlock types.ResponseEndBlock) {
|
|
||||||
return types.ResponseEndBlock{Diffs: app.changes}
|
**Show/Hide Go Example**
|
||||||
}
|
|
||||||
|
.. code-block:: go
|
||||||
|
|
||||||
|
// Update the validator set
|
||||||
|
func (app *PersistentDummyApplication) EndBlock(height uint64) (resEndBlock types.ResponseEndBlock) {
|
||||||
|
return types.ResponseEndBlock{Diffs: app.changes}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assume that one validator changes. The new validator has a power of 10
|
||||||
|
*/
|
||||||
|
ResponseEndBlock requestEndBlock(RequestEndBlock req) {
|
||||||
|
final long currentHeight = req.getHeight();
|
||||||
|
final byte[] validatorPubKey = getValPubKey();
|
||||||
|
|
||||||
|
ResponseEndBlock.Builder builder = ResponseEndBlock.newBuilder();
|
||||||
|
builder.addDiffs(1, Types.Validator.newBuilder().setPower(10L).setPubKey(ByteString.copyFrom(validatorPubKey)).build());
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
Query Connection
|
Query Connection
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
@ -332,33 +467,72 @@ cause Tendermint to not connect to the corresponding peer:
|
||||||
|
|
||||||
Note: these query formats are subject to change!
|
Note: these query formats are subject to change!
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
.. container:: header
|
||||||
if reqQuery.Prove {
|
|
||||||
value, proof, exists := app.state.Proof(reqQuery.Data)
|
**Show/Hide Go Example**
|
||||||
resQuery.Index = -1 // TODO make Proof return index
|
|
||||||
resQuery.Key = reqQuery.Data
|
.. code-block:: go
|
||||||
resQuery.Value = value
|
|
||||||
resQuery.Proof = proof
|
func (app *DummyApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
|
||||||
if exists {
|
if reqQuery.Prove {
|
||||||
resQuery.Log = "exists"
|
value, proof, exists := app.state.Proof(reqQuery.Data)
|
||||||
} else {
|
resQuery.Index = -1 // TODO make Proof return index
|
||||||
resQuery.Log = "does not exist"
|
resQuery.Key = reqQuery.Data
|
||||||
|
resQuery.Value = value
|
||||||
|
resQuery.Proof = proof
|
||||||
|
if exists {
|
||||||
|
resQuery.Log = "exists"
|
||||||
|
} else {
|
||||||
|
resQuery.Log = "does not exist"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
index, value, exists := app.state.Get(reqQuery.Data)
|
||||||
|
resQuery.Index = int64(index)
|
||||||
|
resQuery.Value = value
|
||||||
|
if exists {
|
||||||
|
resQuery.Log = "exists"
|
||||||
|
} else {
|
||||||
|
resQuery.Log = "does not exist"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
} else {
|
.. container:: toggle
|
||||||
index, value, exists := app.state.Get(reqQuery.Data)
|
|
||||||
resQuery.Index = int64(index)
|
.. container:: header
|
||||||
resQuery.Value = value
|
|
||||||
if exists {
|
**Show/Hide Java Example**
|
||||||
resQuery.Log = "exists"
|
|
||||||
} else {
|
.. code-block:: java
|
||||||
resQuery.Log = "does not exist"
|
|
||||||
|
ResponseQuery requestQuery(RequestQuery req) {
|
||||||
|
final boolean isProveQuery = req.getProve();
|
||||||
|
final ResponseQuery.Builder responseBuilder = ResponseQuery.newBuilder();
|
||||||
|
|
||||||
|
if (isProveQuery) {
|
||||||
|
com.app.example.ProofResult proofResult = generateProof(req.getData().toByteArray());
|
||||||
|
final byte[] proofAsByteArray = proofResult.getAsByteArray();
|
||||||
|
|
||||||
|
responseBuilder.setProof(ByteString.copyFrom(proofAsByteArray));
|
||||||
|
responseBuilder.setKey(req.getData());
|
||||||
|
responseBuilder.setValue(ByteString.copyFrom(proofResult.getData()));
|
||||||
|
responseBuilder.setLog(result.getLogValue());
|
||||||
|
} else {
|
||||||
|
byte[] queryData = req.getData().toByteArray();
|
||||||
|
|
||||||
|
final com.app.example.QueryResult result = generateQueryResult(queryData);
|
||||||
|
|
||||||
|
responseBuilder.setIndex(result.getIndex());
|
||||||
|
responseBuilder.setValue(ByteString.copyFrom(result.getValue()));
|
||||||
|
responseBuilder.setLog(result.getLogValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBuilder.build();
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Handshake
|
Handshake
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
@ -377,11 +551,31 @@ the app are synced to the latest block height.
|
||||||
If the app returns a LastBlockHeight of 0, Tendermint will just replay
|
If the app returns a LastBlockHeight of 0, Tendermint will just replay
|
||||||
all blocks.
|
all blocks.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
|
.. container:: header
|
||||||
return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())}
|
|
||||||
}
|
**Show/Hide Go Example**
|
||||||
|
|
||||||
|
.. code-block:: go
|
||||||
|
|
||||||
|
func (app *DummyApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
|
||||||
|
return types.ResponseInfo{Data: cmn.Fmt("{\"size\":%v}", app.state.Size())}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
ResponseInfo requestInfo(RequestInfo req) {
|
||||||
|
final byte[] lastAppHash = getLastAppHash();
|
||||||
|
final long lastHeight = getLastHeight();
|
||||||
|
return ResponseInfo.newBuilder().setLastBlockAppHash(ByteString.copyFrom(lastAppHash)).setLastBlockHeight(lastHeight).build();
|
||||||
|
}
|
||||||
|
|
||||||
Genesis
|
Genesis
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
@ -390,14 +584,45 @@ Genesis
|
||||||
initial validator set. Later on, it may be extended to take parts of the
|
initial validator set. Later on, it may be extended to take parts of the
|
||||||
consensus params.
|
consensus params.
|
||||||
|
|
||||||
::
|
.. container:: toggle
|
||||||
|
|
||||||
// Save the validators in the merkle tree
|
.. container:: header
|
||||||
func (app *PersistentDummyApplication) InitChain(params types.RequestInitChain) {
|
|
||||||
for _, v := range params.Validators {
|
**Show/Hide Go Example**
|
||||||
r := app.updateValidator(v)
|
|
||||||
if r.IsErr() {
|
.. code-block:: go
|
||||||
app.logger.Error("Error updating validators", "r", r)
|
|
||||||
|
// Save the validators in the merkle tree
|
||||||
|
func (app *PersistentDummyApplication) InitChain(params types.RequestInitChain) {
|
||||||
|
for _, v := range params.Validators {
|
||||||
|
r := app.updateValidator(v)
|
||||||
|
if r.IsErr() {
|
||||||
|
app.logger.Error("Error updating validators", "r", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. container:: toggle
|
||||||
|
|
||||||
|
.. container:: header
|
||||||
|
|
||||||
|
**Show/Hide Java Example**
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
/*
|
||||||
|
* all types come from protobuf definition
|
||||||
|
*/
|
||||||
|
ResponseInitChain requestInitChain(RequestInitChain req) {
|
||||||
|
final int validatorsCount = req.getValidatorsCount();
|
||||||
|
final List<Types.Validator> validatorsList = req.getValidatorsList();
|
||||||
|
|
||||||
|
validatorsList.forEach((validator) -> {
|
||||||
|
long power = validator.getPower();
|
||||||
|
byte[] validatorPubKey = validator.getPubKey().toByteArray();
|
||||||
|
|
||||||
|
// do somehing for validator setup in app
|
||||||
|
});
|
||||||
|
|
||||||
|
return ResponseInitChain.newBuilder().build();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue