diff --git a/Dockerfile b/Dockerfile index af8b4c1febce..973fcf2a7b43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ FROM golang:alpine AS build-env # Install minimum necessary dependencies, -ENV PACKAGES curl make git libc-dev bash gcc linux-headers eudev-dev python3 +ENV PACKAGES curl make git libc-dev bash gcc linux-headers eudev-dev python3 plantuml RUN apk add --no-cache $PACKAGES # Set working directory for the build diff --git a/docs/building-modules/msg-services.md b/docs/building-modules/msg-services.md index 6941252f3a76..29927ae8b2c3 100644 --- a/docs/building-modules/msg-services.md +++ b/docs/building-modules/msg-services.md @@ -54,6 +54,10 @@ This method takes care of marshaling the `res` parameter to protobuf and attachi +++ https://github.com/cosmos/cosmos-sdk/blob/d55c1a26657a0af937fa2273b38dcfa1bb3cff9f/proto/cosmos/base/abci/v1beta1/abci.proto#L81-L95 +This diagram shows a typical structure of an `Msg` Service, and how the message propagates through the module. + +![](../uml/transaction_flow.svg) + ## Legacy Amino `Msg`s ### `handler` type diff --git a/docs/core/ocap.md b/docs/core/ocap.md index db8d0b7e5c86..2264d46bff67 100644 --- a/docs/core/ocap.md +++ b/docs/core/ocap.md @@ -70,6 +70,10 @@ gaia app. +++ https://github.com/cosmos/cosmos-sdk/blob/v0.41.4/simapp/app.go#L249-L273 +The following diagram shows the current dependencies between keepers. + +![](../uml/keeper_dependencies.svg) + ## Next {hide} Learn about the [`runTx` middleware](./runtx_middleware.md) {hide} diff --git a/docs/pre.sh b/docs/pre.sh index 47e4329fbf5c..e5a0f9fe131b 100755 --- a/docs/pre.sh +++ b/docs/pre.sh @@ -9,4 +9,6 @@ for D in ../x/*; do fi done -cat ../x/README.md | sed 's/\.\/x/\/modules/g' | sed 's/spec\/README.md//g' | sed 's/\.\.\/docs\/building-modules\/README\.md/\/building-modules\/intro\.html/g' > ./modules/README.md \ No newline at end of file +cat ../x/README.md | sed 's/\.\/x/\/modules/g' | sed 's/spec\/README.md//g' | sed 's/\.\.\/docs\/building-modules\/README\.md/\/building-modules\/intro\.html/g' > ./modules/README.md + +plantuml -tsvg uml/*.puml diff --git a/docs/uml/begin_redelegation_sequence.puml b/docs/uml/begin_redelegation_sequence.puml new file mode 100644 index 000000000000..315f063f464c --- /dev/null +++ b/docs/uml/begin_redelegation_sequence.puml @@ -0,0 +1,50 @@ +@startuml +'https://plantuml.com/sequence-diagram + +title: Redelegation + +msgServer -> keeper : BeginRedelegation(delAddr, valSrcAddr, valDstAddr, sharesAmount) +participant "keeper (staking)" as keeper +keeper -> keeper : get number of sharew +note left: If the delegator has more shares than the total shares in the validator\n(due to rounding errors), then just withdraw the max number of shares. +keeper -> keeper : check the redelegation uses correct denom + +alt valSrcAddr == valDstAddr + keeper --> msgServer : error +end +alt transitive redelegation + keeper --> msgServer : error +end +alt already has max redelegations + keeper --> msgServer : error + note left : this is the number of redelegations for a specific (del, valSrc, valDst) triple\ndefault : 7 +end + + +keeper -> keeper : Unbond(del, valSrc) returns returnAmount +... +note left : See unbonding diagram + +alt returnAmount is zero +keeper -> msgServer : error +end + +keeper -> keeper : Delegate(del, returnAmount, status := valSrc.status, valDst, subtractAccount := false) +note left : See delegation diagram +... + +alt validator is unbonded + keeper -> msgServer : current time +end + +alt unbonding not complete, or just started + database store + keeper -> store : create redelegation object + keeper -> store : insert redelegation in queue, to be processed at the appropriate time +end + +msgServer <-- keeper : completion time of the redelegation +msgServer -> msgServer : emit event: delegator, valSrc, valSrc,\nsharesAmount, completionTime + +@enduml + diff --git a/docs/uml/delegation_sequence.puml b/docs/uml/delegation_sequence.puml new file mode 100644 index 000000000000..ad0286476b8d --- /dev/null +++ b/docs/uml/delegation_sequence.puml @@ -0,0 +1,93 @@ +@startuml +'https://plantuml.com/sequence-diagram + +title: Delegating (currently undelegated funds delegator) + +participant "msgServer (staking)" +participant "keeper (staking)" as keeper +participant validator +participant keeper.bankKeeper +participant vestingAccount +participant ctx.EventManager + +database store + +"msgServer (staking)" -> keeper : Delegate(Context, DelegatorAddress, Amount, Validator, tokenSrc := Unbonded) + +alt exchange rate is invalid (tokens in validator is 0) + keeper --> "msgServer (staking)" : error +end + +alt perform a new delegation + keeper -> keeper : delegation := create delegation object + keeper -> keeper : BeforeDelegationCreated hook + note left: Calls IncrementValidatorPeriod (Used to calculate distribution) in keeper/validator.go +else delegation exists, more tokens being added + keeper -> keeper : BeforeDelegationModified hook + note left: withdraw current delegation rewards (and increment period) +end + +alt delegating from an account (subtractTokens == true) + keeper -> keeper.bankKeeper : DelegateCoinsFromAccountToModule + group DelegateCoinsFromAccountToModule function + keeper.bankKeeper -> keeper.bankKeeper : DelegateCoinsFromAccountToModule + keeper.bankKeeper -> keeper.bankKeeper : DelegateCoins + group DelegateCoins function + keeper.bankKeeper --> keeper.bankKeeper : Check the delegator has enough balances of all tokens delegated + keeper.bankKeeper --> keeper.bankKeeper : Track delegation (register that it exists to keep track of it) + alt validator is currently bonded + keeper.bankKeeper --> store : Transfer tokens from delegator to BondedTokensPool. + else validator is currently unbonded or unbonding + keeper.bankKeeper --> store : Transfer tokens from delegator to NotBondedTokensPool. + end + group trackDelegation function + keeper.bankKeeper -> keeper.bankKeeper : trackDelegation + alt delegator is a vesting account + keeper.bankKeeper -> vestingAccount : keep track of this delegation + end + end + end + end + keeper <-- keeper.bankKeeper : nil (success) +else moving tokens between pools (subtractTokens == false) + alt delegator tokens are not bonded but validator is bonded + keeper -> keeper.bankKeeper : SendCoinsFromModuleToModule(notBondedPool, bondedPool, coins) + else delegator tokens are bonded but validator is not bonded + keeper -> keeper.bankKeeper : SendCoinsFromModuleToModule(bondedPool, notBondedPool, coins) + end + group SendCoins function + keeper.bankKeeper -> keeper.bankKeeper : SendCoins + keeper.bankKeeper -> ctx.EventManager : Emit TransferEvent(to, from, amount) + alt amount of spendable (balance - locked) coins too low + keeper <-- keeper.bankKeeper : error + end + keeper.bankKeeper -> store : subtract balance from sender + keeper.bankKeeper -> store : add balance to recipient + end +end + +keeper -> validator : AddTokensFromDel +validator -> validator : calculate number of shares to issue +note left: If there are no shares (validator being created) then 1 token = 1 share.\nIf there are already shares, then\nadded shares = (added tokens amount) * (current validator shares) / (current validator tokens) + +validator -> validator : add delegated tokens to validator +keeper <-- validator : validator, addedShares +keeper -> store : update validator state +keeper -> keeper: calculate new validator's power +note left : Number of tokens divided by PowerReduction (default: 1,000,000,000,000,000,000 = 10^18) +alt validator is not jailed + keeper -> store : update validator's power in power index + note left : the power index has entries shaped as 35 || power || address.\nThis makes the validators sorted by power, high to low. +end + +keeper -> keeper : AfterDelegationModified hook +note left: Calls initializeDelegation\nStore the previous period\nCalculate the number of tokens from shares\n(shares the delegator has) * (tokens in delegation object)/(total tokens delegated to the validator)\nStore delegation starting info. +"msgServer (staking)" <-- keeper : newShares (ignored by Delegate function) + + +"msgServer (staking)" -> "msgServer (staking)" : Emit event: Delegation(ValidatorAddress) +"msgServer (staking)" -> "msgServer (staking)" : Emit event: Message(DelegatorAddress) +"msgServer (staking)" -> "msgServer (staking)" : telemetry(Amount, Denom) + +@enduml + diff --git a/docs/uml/keeper_dependencies.puml b/docs/uml/keeper_dependencies.puml new file mode 100644 index 000000000000..5a80ad9f33d5 --- /dev/null +++ b/docs/uml/keeper_dependencies.puml @@ -0,0 +1,36 @@ +@startuml +'https://plantuml.com/class-diagram + +title: The dependencies between Keepers (Feb 2021) + +abstract class Staking +abstract class Distribution +abstract class Slashing +abstract class Evidence +abstract class Bank +abstract class "Auth/Account" as Auth +abstract class Gov +abstract class Mint + +Staking <|-- Mint +Bank <|-- Mint + +Staking <|-- Gov +Bank <|-- Gov +Auth <|-- Gov + +Auth <|-- Bank + +Bank <|-- Distribution +Auth <|-- Distribution +Staking <|-- Distribution + +Staking <|-- Evidence +Slashing <|-- Evidence + +Staking <|-- Slashing + +Auth <|-- Staking +Bank <|-- Staking + +@enduml diff --git a/docs/uml/transaction_flow.puml b/docs/uml/transaction_flow.puml new file mode 100644 index 000000000000..bf01f04f68b7 --- /dev/null +++ b/docs/uml/transaction_flow.puml @@ -0,0 +1,22 @@ +What happens after a transaction is unmarshalled and is processed by the SDK? + +@startuml +'https://plantuml.com/sequence-diagram + +actor User +User -> baseApp : Transaction Type +baseApp -> router : Route(ctx, msgRoute) +router --> baseApp : handler +baseApp -> handler: Msg(Context, Msg(...)) +handler -> msgServer : (Context, Msg) +alt addresses invalid, denominations wrong, etc. +msgServer --> handler : error +end +msgServer -> keeper : perform action, update context +keeper --> msgServer : results, error code +msgServer -> Context.EventManager : Emit relevant events +msgServer -> msgServer : maybe wrap results in more structure +msgServer --> handler : result, error code +baseApp <-- handler : results, error code + +@enduml diff --git a/docs/uml/unbond_sequence.puml b/docs/uml/unbond_sequence.puml new file mode 100644 index 000000000000..bfe385b42b69 --- /dev/null +++ b/docs/uml/unbond_sequence.puml @@ -0,0 +1,51 @@ +@startuml +'https://plantuml.com/sequence-diagram + +title: Undelegate + +msgServer -> keeper : Undelegate(delAddr, valAddr, tokenAmount) + +keeper -> keeper : calculate number of shares the tokenAmount represents + +alt wrong denom + msgServer <-- keeper : error +end + +group Unbond(delAddr, valAddr, shares) + keeper -> keeper: BeforeDelegationSharesModified hook + alt no such delegation + keeper --> msgServer : error + end + alt not enough shares + keeper --> msgServer : error + end + alt delegator is the operator of the validator\nand validator is not already jailed\nand unbonding would put self-delegation under min threshold + keeper -> keeper : jail the validator, but proceed with unbonding + note left : Default min delegation threshold : 1 share + end + + database store + + alt complete unbonding, all shares removed + keeper -> store : remove delegation object + else there are still shares delegated (not a complete undbonding) + keeper -> store : update delegation object + keeper -> keeper : AfterDelegationModified hook + end + + keeper -> store : update validator power index + keeper -> store : update validator information (including token amount) + + alt validator status is "unbonded" and it has no more tokens + keeper -> store : delete the validator + note right : otherwise, do this in EndBlock once validator is unbonded + end +end + +alt validator is bonded + keeper -> bankKeeper : send tokens from bonded pool to not bonded pool +end + +msgServer -> msgServer : emit event : EventTypeUnbond(delAddr, valAddr, tokenAmount, completion time) + +@enduml diff --git a/x/staking/spec/03_messages.md b/x/staking/spec/03_messages.md index fb6eb06837fa..b6fdec6f7803 100644 --- a/x/staking/spec/03_messages.md +++ b/x/staking/spec/03_messages.md @@ -80,6 +80,8 @@ tracked in validator object in the `Validators` index. It is possible to delegate to a jailed validator, the only difference being it will not be added to the power index until it is unjailed. +![](docs/uml/delegation_sequence.svg) + ## Msg/Undelegate The `Msg/Undelegate` service message allows delegators to undelegate their tokens from @@ -112,6 +114,8 @@ When this service message is processed the following actions occur: - if there are no more `Shares` in the delegation, then the delegation object is removed from the store - under this situation if the delegation is the validator's self-delegation then also jail the validator. +![](docs/uml/unbond_sequence.svg) + ## Msg/BeginRedelegate The redelegation command allows delegators to instantly switch validators. Once @@ -146,3 +150,5 @@ When this service message is processed the following actions occur: - Delegate the token worth to the destination validator, possibly moving tokens back to the bonded state. - if there are no more `Shares` in the source delegation, then the source delegation object is removed from the store - under this situation if the delegation is the validator's self-delegation then also jail the validator. + +![](docs/uml/begin_redelegation_sequence.svg)