Merge branch 'master' into sunny/change-pubkey-adr
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# ADR Creation Process
|
||||
|
||||
1. Copy the `adr-template.md` file. Use the following filename pattern: `adr-next_number-title.md`
|
||||
2. Create a draft Pull Request if you want to get an early feedback.
|
||||
3. Make sure the context and a solution is clear and well documented.
|
||||
4. Add an entry to a list in the [README](./README.md) file.
|
||||
5. Create a Pull Request to propose a new ADR.
|
||||
|
||||
|
||||
## ADR life cycle
|
||||
|
||||
ADR creation is an **iterative** process. Instead of trying to solve all decisions in a single ADR pull request, we MUST firstly understand the problem and collect feedback through a GitHub Issue.
|
||||
|
||||
1. Every proposal SHOULD start with a new GitHub Issue or be a result of existing Issues. The Issue should contain just a brief proposal summary.
|
||||
|
||||
2. Once the motivation is validated, a GitHub Pull Request (PR) is created with a new document based on the `adr-template.md`.
|
||||
|
||||
3. An ADR doesn't have to arrive to `master` with an _accepted_ status in a single PR. If the motivation is clear and the solution is sound, we SHOULD be able to merge it and keep a _proposed_ status. It's preferable to have an iterative approach rather than long, not merged Pull Requests.
|
||||
|
||||
4. If a _proposed_ ADR is merged, then it should clearly document outstanding issues either in ADR document notes or in a GitHub Issue.
|
||||
|
||||
5. The PR SHOULD always be merged. In the case of a faulty ADR, we still prefer to merge it with a _rejected_ status. The only time the ADR SHOULD NOT be merged is if the author abandons it.
|
||||
|
||||
6. Merged ADRs SHOULD NOT be pruned.
|
||||
|
||||
|
||||
### ADR status
|
||||
|
||||
Status has two components:
|
||||
|
||||
```
|
||||
{CONSENSUS STATUS} {IMPLEMENTATION STATUS}
|
||||
```
|
||||
|
||||
IMPLEMENTATION STATUS is either `Implemented` or `Not Implemented`.
|
||||
|
||||
#### Consensus Status
|
||||
|
||||
```
|
||||
DRAFT -> PROPOSED -> LAST CALL yyyy-mm-dd -> ACCEPTED | REJECTED -> SUPERSEEDED by ADR-xxx
|
||||
\ |
|
||||
\ |
|
||||
v v
|
||||
ABANDONED
|
||||
```
|
||||
|
||||
|
||||
+ `DRAFT`: [optional] an ADR which is work in progress, not being ready for a general review. This is to present an early work and get an early feedback in a Draft Pull Request form.
|
||||
+ `PROPOSED`: an ADR covering a full solution architecture and still in the review - project stakeholders haven't reached an agreed yet.
|
||||
+ `LAST CALL <date for the last call>`: [optional] clear notify that we are close to accept updates. Changing a status to `LAST CALL` means that social consensus (of Cosmos SDK maintainers) has been reached and we still want to give it a time to let the community react or analyze.
|
||||
+ `ACCEPTED`: ADR which will represent a currently implemented or to be implemented architecture design.
|
||||
+ `REJECTED`: ADR can go from PROPOSED or ACCEPTED to rejected if the consensus among project stakeholders will decide so.
|
||||
+ `SUPERSEEDED by ADR-xxx`: ADR which has been superseded by a new ADR.
|
||||
+ `ABANDONED`: the ADR is no longer pursued by the original authors.
|
||||
|
||||
|
||||
## Language used in ADR
|
||||
|
||||
+ The context/background should be written in the present tense.
|
||||
+ Avoid using a first, personal form.
|
||||
@@ -8,8 +8,15 @@ parent:
|
||||
|
||||
This is a location to record all high-level architecture decisions in the Cosmos-SDK.
|
||||
|
||||
An Architectural Decision (**AD**) is a software design choice that addresses a functional or non-functional requirement that is architecturally significant.
|
||||
An Architecturally Significant Requirement (**ASR**) is a requirement that has a measurable effect on a software system’s architecture and quality.
|
||||
An Architectural Decision Record (**ADR**) captures a single AD, such as often done when writing personal notes or meeting minutes; the collection of ADRs created and maintained in a project constitute its decision log. All these are within the topic of Architectural Knowledge Management (AKM).
|
||||
|
||||
You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t).
|
||||
|
||||
## Rationale
|
||||
|
||||
ADRs are intended to be the primary mechanism for proposing new feature designs and new processes, for collecting community input on an issue, and for documenting the design decisions.
|
||||
An ADR should provide:
|
||||
|
||||
- Context on the relevant goals and the current state
|
||||
@@ -25,19 +32,29 @@ it stands today.
|
||||
|
||||
If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match.
|
||||
|
||||
Note the context/background should be written in the present tense.
|
||||
|
||||
Please add a entry below in your Pull Request for an ADR.
|
||||
## Creating new ADR
|
||||
|
||||
Read about the [PROCESS](./PROCESS.md).
|
||||
|
||||
## ADR Table of Contents
|
||||
|
||||
### Accepted
|
||||
|
||||
- [ADR 001: Coin Source Tracing](./adr-001-coin-source-tracing.md)
|
||||
- [ADR 002: SDK Documentation Structure](./adr-002-docs-structure.md)
|
||||
- [ADR 003: Dynamic Capability Store](./adr-003-dynamic-capability-store.md)
|
||||
- [ADR 004: Split Denomination Keys](./adr-004-split-denomination-keys.md)
|
||||
- [ADR 006: Secret Store Replacement](./adr-006-secret-store-replacement.md)
|
||||
- [ADR 009: Evidence Module](./adr-009-evidence-module.md)
|
||||
- [ADR 010: Modular AnteHandler](./adr-010-modular-antehandler.md)
|
||||
- [ADR 019: Protocol Buffer State Encoding](./adr-019-protobuf-state-encoding.md)
|
||||
- [ADR 020: Protocol Buffer Transaction Encoding](./adr-020-protobuf-transaction-encoding.md)
|
||||
- [ADR 026: IBC Client Recovery Mechanisms](./adr-026-ibc-client-recovery-mechanisms.md)
|
||||
- [ADR 029: Fee Grant Module](./adr-029-fee-grant-module.md)
|
||||
|
||||
### Proposed
|
||||
|
||||
- [ADR 003: Dynamic Capability Store](./adr-003-dynamic-capability-store.md)
|
||||
- [ADR 004: Split Denomination Keys](./adr-004-split-denomination-keys.md)
|
||||
- [ADR 011: Generalize Genesis Accounts](./adr-011-generalize-genesis-accounts.md)
|
||||
- [ADR 012: State Accessors](./adr-012-state-accessors.md)
|
||||
- [ADR 013: Metrics](./adr-013-metrics.md)
|
||||
@@ -45,12 +62,12 @@ Please add a entry below in your Pull Request for an ADR.
|
||||
- [ADR 016: Validator Consensus Key Rotation](./adr-016-validator-consensus-key-rotation.md)
|
||||
- [ADR 017: Historical Header Module](./adr-017-historical-header-module.md)
|
||||
- [ADR 018: Extendable Voting Periods](./adr-018-extendable-voting-period.md)
|
||||
- [ADR 019: Protocol Buffer State Encoding](./adr-019-protobuf-state-encoding.md)
|
||||
- [ADR 020: Protocol Buffer Transaction Encoding](./adr-020-protobuf-transaction-encoding.md)
|
||||
- [ADR 021: Protocol Buffer Query Encoding](./adr-021-protobuf-query-encoding.md)
|
||||
- [ADR 022: Custom baseapp panic handling](./adr-022-custom-panic-handling.md)
|
||||
- [ADR 023: Protocol Buffer Naming and Versioning](./adr-023-protobuf-naming.md)
|
||||
- [ADR 024: Coin Metadata](./adr-024-coin-metadata.md)
|
||||
- [ADR 025: IBC Passive Channels](./adr-025-ibc-passive-channels.md)
|
||||
- [ADR 026: IBC Client Recovery Mechanisms](./adr-026-ibc-client-recovery-mechanisms.md)
|
||||
- [ADR 027: Deterministic Protobuf Serialization](./adr-027-deterministic-protobuf-serialization.md)
|
||||
- [ADR 028: Public Key Addresses](./adr-028-public-key-addresses.md)
|
||||
- [ADR 031: Protobuf Msg Services](./adr-031-msg-service.md)
|
||||
- [ADR 032: Typed Events](./adr-032-typed-events.md)
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
- 2020 May 14: Describe public key encoding
|
||||
- 2020 June 08: Store `TxBody` and `AuthInfo` as bytes in `SignDoc`; Document `TxRaw` as broadcast and storage type.
|
||||
- 2020 August 07: Use ADR 027 for serializing `SignDoc`.
|
||||
- 2020 August 19: Move sequence field from `SignDoc` to `SignerInfo`.
|
||||
- 2020 August 19: Move sequence field from `SignDoc` to `SignerInfo`, as discussed in [#6966](https://github.com/cosmos/cosmos-sdk/issues/6966).
|
||||
- 2020 September 25: Remove `PublicKey` type in favor of `secp256k1.PubKey`, `ed25519.PubKey` and `multisig.LegacyAminoPubKey`.
|
||||
- 2020 October 15: Add `GetAccount` and `GetAccountWithHeight` methods to the `AccountRetriever` interface.
|
||||
|
||||
## Status
|
||||
|
||||
@@ -315,6 +316,8 @@ and messages.
|
||||
|
||||
```go
|
||||
type AccountRetriever interface {
|
||||
GetAccount(clientCtx Context, addr sdk.AccAddress) (client.Account, error)
|
||||
GetAccountWithHeight(clientCtx Context, addr sdk.AccAddress) (client.Account, int64, error)
|
||||
EnsureExists(clientCtx client.Context, addr sdk.AccAddress) error
|
||||
GetAccountNumberSequence(clientCtx client.Context, addr sdk.AccAddress) (uint64, uint64, error)
|
||||
}
|
||||
|
||||
@@ -109,12 +109,12 @@ func (q Querier) QueryBalance(ctx context.Context, params *types.QueryBalancePar
|
||||
### Custom Query Registration and Routing
|
||||
|
||||
Query server implementations as above would be registered with `AppModule`s using
|
||||
a new method `RegisterQueryServer(grpc.Server)` which could be implemented simply
|
||||
a new method `RegisterQueryService(grpc.Server)` which could be implemented simply
|
||||
as below:
|
||||
|
||||
```go
|
||||
// x/bank/module.go
|
||||
func (am AppModule) RegisterQueryServer(server grpc.Server) {
|
||||
func (am AppModule) RegisterQueryService(server grpc.Server) {
|
||||
types.RegisterQueryServer(server, keeper.Querier{am.keeper})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
# ADR 028: Public Key Addresses
|
||||
|
||||
## Changelog
|
||||
|
||||
- 2020/08/18: Initial version
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Abstract
|
||||
|
||||
This ADR defines a canonical 20-byte address format for new public key algorithms, multisig public keys, and module
|
||||
accounts using string prefixes.
|
||||
|
||||
## Context
|
||||
|
||||
Issue [\#3685](https://github.com/cosmos/cosmos-sdk/issues/3685) identified that public key
|
||||
address spaces are currently overlapping. One initial proposal was extending the address length and
|
||||
adding prefixes for different types of addresses.
|
||||
|
||||
@ethanfrey explained an alternate approach originally used in https://github.com/iov-one/weave:
|
||||
|
||||
> I spent quite a bit of time thinking about this issue while building weave... The other cosmos Sdk.
|
||||
|
||||
> Basically I define a condition to be a type and format as human readable string with some binary data appended. This condition is hashed into an Address (again at 20 bytes). The use of this prefix makes it impossible to find a preimage for a given address with a different condition (eg ed25519 vs secp256k1).
|
||||
|
||||
> This is explained in depth here https://weave.readthedocs.io/en/latest/design/permissions.html
|
||||
|
||||
> And the code is here, look mainly at the top where we process conditions. https://github.com/iov-one/weave/blob/master/conditions.go
|
||||
|
||||
And explained how this approach should be sufficiently collision resistant:
|
||||
> Yeah, AFAIK, 20 bytes should be collision resistance when the preimages are unique and not malleable. A space of 2^160 would expect some collision to be likely around 2^80 elements (birthday paradox). And if you want to find a collision for some existing element in the database, it is still 2^160. 2^80 only is if all these elements are written to state.
|
||||
|
||||
> The good example you brought up was eg. a public key bytes being a valid public key on two algorithms supported by the codec. Meaning if either was broken, you would break accounts even if they were secured with the safer variant. This is only as the issue when no differentiating type info is present in the preimage (before hashing into an address).
|
||||
|
||||
> I would like to hear an argument if the 20 bytes space is an actual issue for security, as I would be happy to increase my address sizes in weave. I just figured cosmos and ethereum and bitcoin all use 20 bytes, it should be good enough. And the arguments above which made me feel it was secure. But I have not done a deeper analysis.
|
||||
|
||||
In discussions in [\#5694](https://github.com/cosmos/cosmos-sdk/issues/5694), we agreed to go with an
|
||||
approach similar to this where essentially we take the first 20 bytes of the `sha256` hash of
|
||||
the key type concatenated with the key bytes, summarized as `Sha256(KeyTypePrefix || Keybytes)[:20]`.
|
||||
|
||||
## Decision
|
||||
|
||||
### Legacy Public Key Addresses Don't Change
|
||||
|
||||
`secp256k1` and multisig public keys are currently in use in existing Cosmos SDK zones. They use the following
|
||||
address formats:
|
||||
|
||||
- secp256k1: `ripemd160(sha256(pk_bytes))[:20]`
|
||||
- legacy amino multisig: `sha256(aminoCdc.Marshal(pk))[:20]`
|
||||
|
||||
We don't want to change existing addresses. So the addresses for these two key types will remain the same.
|
||||
|
||||
The current multisig public keys use amino serialization to generate the address. We will retain
|
||||
those public keys and their address formatting, and call them "legacy amino" multisig public keys
|
||||
in protobuf. We will also create multisig public keys without amino addresses to be described below.
|
||||
|
||||
|
||||
### Canonical Address Format
|
||||
|
||||
We have three types of accounts we would like to create addresses for in the future:
|
||||
- regular public key addresses for new signature algorithms (ex. `sr25519`).
|
||||
- public key addresses for multisig public keys that don't use amino encoding
|
||||
- module accounts: basically any accounts which cannot sign transactions and
|
||||
which are managed internally by modules
|
||||
|
||||
To address all of these use cases we propose the following basic `AddressHash` function,
|
||||
based on the discussions in [\#5694](https://github.com/cosmos/cosmos-sdk/issues/5694):
|
||||
|
||||
```go
|
||||
func AddressHash(prefix string, contents []byte) []byte {
|
||||
preImage := []byte(prefix)
|
||||
if len(contents) != 0 {
|
||||
preImage = append(preImage, 0)
|
||||
preImage = append(preImage, contents...)
|
||||
}
|
||||
return sha256.Sum256(preImage)[:20]
|
||||
}
|
||||
```
|
||||
|
||||
`AddressHash` always take a string `prefix` as a starting point which should represent the
|
||||
type of public key (ex. `sr25519`) or module account being used (ex. `staking` or `group`).
|
||||
For public keys, the `contents` parameter is used to specify the binary contents of the public
|
||||
key. For module accounts, `contents` can be left empty (for modules which don't manage "sub-accounts"),
|
||||
or can be some module-specific content to specify different pools (ex. `bonded` or `not-bonded` for `staking`)
|
||||
or managed accounts (ex. different accounts managed by the `group` module).
|
||||
|
||||
In the `preImage`, the byte value `0` is used as the separator between `prefix` and `contents`. This is a logical
|
||||
choice given that `0` is an invalid value for a string character and is commonly used as a null terminator.
|
||||
|
||||
### Canonical Public Key Address Prefixes
|
||||
|
||||
All public key types will have a unique protobuf message type such as:
|
||||
|
||||
```proto
|
||||
package cosmos.crypto.sr25519;
|
||||
|
||||
message PubKey {
|
||||
bytes key = 1;
|
||||
}
|
||||
```
|
||||
|
||||
All protobuf messages have unique fully qualified names, in this example `cosmos.crypto.sr25519.PubKey`.
|
||||
These names are derived directly from .proto files in a standardized way and used
|
||||
in other places such as the type URL in `Any`s. Since there is an easy and obvious
|
||||
way to get this name for every protobuf type, we can use this message name as the
|
||||
key type `prefix` when creating addresses. For all basic public keys, `contents`
|
||||
should just be the raw unencoded public key bytes.
|
||||
|
||||
Thus the canonical address for new public key types would be `AddressHash(proto.MessageName(pk), pk.Bytes)`.
|
||||
|
||||
### Multisig Addresses
|
||||
|
||||
For new multisig public keys, we define a custom address format not based on any encoding scheme
|
||||
(amino or protobuf). This avoids issues with non-determinism in the encoding scheme. It also
|
||||
ensures that multisig public keys which differ simply in the ordering of keys have the same
|
||||
address by sorting child public keys first.
|
||||
|
||||
First we define a proto message for multisig public keys:
|
||||
```proto
|
||||
package cosmos.crypto.multisig;
|
||||
|
||||
message PubKey {
|
||||
uint32 threshold = 1;
|
||||
repeated google.protobuf.Any public_keys = 2;
|
||||
}
|
||||
```
|
||||
|
||||
We define the following `Address()` function for this public key:
|
||||
|
||||
```
|
||||
func (multisig PubKey) Address() {
|
||||
// first gather all the addresses of each nested public key
|
||||
var addresses [][]byte
|
||||
for key := range multisig.Keys {
|
||||
addresses = append(joinedAddresses, key.Address())
|
||||
}
|
||||
|
||||
// then sort them in ascending order
|
||||
addresses = Sort(addresses)
|
||||
|
||||
// then concatenate them together
|
||||
var joinedAddresses []byte
|
||||
for addr := range addresses {
|
||||
joinedAddresses := append(joinedAddresses, addr...)
|
||||
}
|
||||
|
||||
// form the string prefix from the message name (cosmos.crypto.multisig.PubKey) and the threshold joined together
|
||||
prefix := fmt.Sprintf("%s/%d", proto.MessageName(multisig), multisig.Threshold)
|
||||
|
||||
// use the standard AddressHash function
|
||||
return AddressHash(prefix, joinedAddresses)
|
||||
}
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- a simple algorithm for generating addresses for new public keys and module accounts
|
||||
|
||||
### Negative
|
||||
- addresses do not communicate key type, a prefixed approach would have done this
|
||||
|
||||
### Neutral
|
||||
- protobuf message names are used as key type prefixes
|
||||
|
||||
## References
|
||||
@@ -0,0 +1,162 @@
|
||||
# ADR 029: Fee Grant Module
|
||||
|
||||
## Changelog
|
||||
|
||||
- 2020/08/18: Initial Draft
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
In order to make blockchain transactions, the signing account must possess a sufficient balance of the right denomination
|
||||
in order to pay fees. There are classes of transactions where needing to maintain a wallet with sufficient fees is a
|
||||
barrier to adoption.
|
||||
|
||||
For instance, when proper permissions are setup, someone may temporarily delegate the ability to vote on proposals to
|
||||
a "burner" account that is stored on a mobile phone with only minimal security.
|
||||
|
||||
Other use cases include workers tracking items in a supply chain or farmers submitting field data for analytics
|
||||
or compliance purposes.
|
||||
|
||||
For all of these use cases, UX would be significantly enhanced by obviating the need for these accounts to always
|
||||
maintain the appropriate fee balance. This is especially true if we wanted to achieve enterprise adoption for something
|
||||
like supply chain tracking.
|
||||
|
||||
While one solution would be to have a service that fills up these accounts automatically with the appropriate fees, a better UX
|
||||
would be provided by allowing these accounts to pull from a common fee pool account with proper spending limits.
|
||||
A single pool would reduce the churn of making lots of small "fill up" transactions and also more effectively leverages
|
||||
the resources of the organization setting up the pool.
|
||||
|
||||
## Decision
|
||||
|
||||
As a solution we propose a module, `x/feegrant` which allows one account, the "granter" to grant another account, the "grantee"
|
||||
an allowance to spend the granter's account balance for fees within certain well-defined limits.
|
||||
|
||||
Fee allowances are defined by the extensible `FeeAllowanceI` interface:
|
||||
|
||||
```go
|
||||
type FeeAllowanceI {
|
||||
// Accept can use fee payment requested as well as timestamp/height of the current block
|
||||
// to determine whether or not to process this. This is checked in
|
||||
// Keeper.UseGrantedFees and the return values should match how it is handled there.
|
||||
//
|
||||
// If it returns an error, the fee payment is rejected, otherwise it is accepted.
|
||||
// The FeeAllowance implementation is expected to update it's internal state
|
||||
// and will be saved again after an acceptance.
|
||||
//
|
||||
// If remove is true (regardless of the error), the FeeAllowance will be deleted from storage
|
||||
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
|
||||
Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (remove bool, err error)
|
||||
}
|
||||
```
|
||||
|
||||
Two basic fee allowance types, `BasicFeeAllowance` and `PeriodicFeeAllowance` are defined to support known use cases:
|
||||
|
||||
```proto
|
||||
// BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens
|
||||
// that optionally expires. The delegatee can use up to SpendLimit to cover fees.
|
||||
message BasicFeeAllowance {
|
||||
// spend_limit specifies the maximum amount of tokens that can be spent
|
||||
// by this allowance and will be updated as tokens are spent. If it is
|
||||
// empty, there is no spend limit and any amount of coins can be spent.
|
||||
repeated cosmos_sdk.v1.Coin spend_limit = 1;
|
||||
|
||||
// expires_at specifies an optional time when this allowance expires
|
||||
ExpiresAt expiration = 2;
|
||||
}
|
||||
|
||||
// PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap,
|
||||
// as well as a limit per time period.
|
||||
message PeriodicFeeAllowance {
|
||||
BasicFeeAllowance basic = 1;
|
||||
|
||||
// period specifies the time duration in which period_spend_limit coins can
|
||||
// be spent before that allowance is reset
|
||||
Duration period = 2;
|
||||
|
||||
// period_spend_limit specifies the maximum number of coins that can be spent
|
||||
// in the period
|
||||
repeated cosmos_sdk.v1.Coin period_spend_limit = 3;
|
||||
|
||||
// period_can_spend is the number of coins left to be spent before the period_reset time
|
||||
repeated cosmos_sdk.v1.Coin period_can_spend = 4;
|
||||
|
||||
// period_reset is the time at which this period resets and a new one begins,
|
||||
// it is calculated from the start time of the first transaction after the
|
||||
// last period ended
|
||||
ExpiresAt period_reset = 5;
|
||||
}
|
||||
|
||||
// ExpiresAt is a point in time where something expires.
|
||||
// It may be *either* block time or block height
|
||||
message ExpiresAt {
|
||||
oneof sum {
|
||||
google.protobuf.Timestamp time = 1;
|
||||
uint64 height = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Duration is a repeating unit of either clock time or number of blocks.
|
||||
message Duration {
|
||||
oneof sum {
|
||||
google.protobuf.Duration duration = 1;
|
||||
uint64 blocks = 2;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Allowances can be granted and revoked using `MsgGrantFeeAllowance` and `MsgRevokeFeeAllowance`:
|
||||
|
||||
```proto
|
||||
message MsgGrantFeeAllowance {
|
||||
string granter = 1;
|
||||
string grantee = 2;
|
||||
google.protobuf.Any allowance = 3;
|
||||
}
|
||||
|
||||
// MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee.
|
||||
message MsgRevokeFeeAllowance {
|
||||
string granter = 1;
|
||||
string grantee = 2;
|
||||
}
|
||||
```
|
||||
|
||||
In order to use allowances in transactions, we add a new field `granter` to the transaction `Fee` type:
|
||||
```proto
|
||||
package cosmos.tx.v1beta1;
|
||||
|
||||
message Fee {
|
||||
repeated cosmos.base.v1beta1.Coin amount = 1;
|
||||
uint64 gas_limit = 2;
|
||||
string payer = 3;
|
||||
string granter = 4;
|
||||
}
|
||||
```
|
||||
|
||||
`granter` must either be left empty or must correspond to an account which has granted
|
||||
a fee allowance to fee payer (either the first signer or the value of the `payer` field).
|
||||
|
||||
A new `AnteDecorator` named `DeductGrantedFeeDecorator` will be created in order to process transactions with `fee_payer`
|
||||
set and correctly deduct fees based on fee allowances.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- improved UX for use cases where it is cumbersome to maintain an account balance just for fees
|
||||
|
||||
### Negative
|
||||
|
||||
### Neutral
|
||||
|
||||
- a new field must be added to the transaction `Fee` message and a new `AnteDecorator` must be
|
||||
created to use it
|
||||
|
||||
## References
|
||||
|
||||
- Blog article describing initial work: https://medium.com/regen-network/hacking-the-cosmos-cosmwasm-and-key-management-a08b9f561d1b
|
||||
- Initial public specification: https://gist.github.com/aaronc/b60628017352df5983791cad30babe56
|
||||
- Original subkeys proposal from B-harvest which influenced this design: https://github.com/cosmos/cosmos-sdk/issues/4480
|
||||
@@ -0,0 +1,238 @@
|
||||
# ADR 031: Protobuf Msg Services
|
||||
|
||||
## Changelog
|
||||
|
||||
- 2020-10-05: Initial Draft
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Abstract
|
||||
|
||||
We want to leverage protobuf `service` definitions for defining `Msg`s which will give us significant developer UX
|
||||
improvements in terms of the code that is generated and the fact that return types will now be well defined.
|
||||
|
||||
## Context
|
||||
|
||||
Currently `Msg` handlers in the Cosmos SDK do have return values that are placed in the `data` field of the response.
|
||||
These return values, however, are not specified anywhere except in the golang handler code.
|
||||
|
||||
In early conversations [it was proposed](https://docs.google.com/document/d/1eEgYgvgZqLE45vETjhwIw4VOqK-5hwQtZtjVbiXnIGc/edit)
|
||||
that `Msg` return types be captured using a protobuf extension field, ex:
|
||||
|
||||
```protobuf
|
||||
package cosmos.gov;
|
||||
|
||||
message MsgSubmitProposal
|
||||
option (cosmos_proto.msg_return) = “uint64”;
|
||||
string delegator_address = 1;
|
||||
string validator_address = 2;
|
||||
repeated sdk.Coin amount = 3;
|
||||
}
|
||||
```
|
||||
|
||||
This was never adopted, however.
|
||||
|
||||
Having a well-specified return value for `Msg`s would improve client UX. For instance,
|
||||
in `x/gov`, `MsgSubmitProposal` returns the proposal ID as a big-endian `uint64`.
|
||||
This isn’t really documented anywhere and clients would need to know the internals
|
||||
of the SDK to parse that value and return it to users.
|
||||
|
||||
Also, there may be cases where we want to use these return values programatically.
|
||||
For instance, https://github.com/cosmos/cosmos-sdk/issues/7093 proposes a method for
|
||||
doing inter-module Ocaps using the `Msg` router. A well-defined return type would
|
||||
improve the developer UX for this approach.
|
||||
|
||||
In addition, handler registration of `Msg` types tends to add a bit of
|
||||
boilerplate on top of keepers and is usually done through manual type switches.
|
||||
This isn't necessarily bad, but it does add overhead to creating modules.
|
||||
|
||||
## Decision
|
||||
|
||||
We decide to use protobuf `service` definitions for defining `Msg`s as well as
|
||||
the code generated by them as a replacement for `Msg` handlers.
|
||||
|
||||
Below we define how this will look for the `SubmitProposal` message from `x/gov` module.
|
||||
We start with a `Msg` `service` definition:
|
||||
|
||||
```proto
|
||||
package cosmos.gov;
|
||||
|
||||
service Msg {
|
||||
rpc SubmitProposal(MsgSubmitProposal) returns (MsgSubmitProposalResponse);
|
||||
}
|
||||
|
||||
// Note that for backwards compatibility this uses MsgSubmitProposal as the request
|
||||
// type instead of the more canonical MsgSubmitProposalRequest
|
||||
message MsgSubmitProposal {
|
||||
google.protobuf.Any content = 1;
|
||||
string proposer = 2;
|
||||
}
|
||||
|
||||
message MsgSubmitProposalResponse {
|
||||
uint64 proposal_id;
|
||||
}
|
||||
```
|
||||
|
||||
While this is most commonly used for gRPC, overloading protobuf `service` definitions like this does not violate
|
||||
the intent of the [protobuf spec](https://developers.google.com/protocol-buffers/docs/proto3#services) which says:
|
||||
> If you don’t want to use gRPC, it’s also possible to use protocol buffers with your own RPC implementation.
|
||||
With this approach, we would get an auto-generated `MsgServer` interface:
|
||||
|
||||
In addition to clearly specifying return types, this has the benefit of generating client and server code. On the server
|
||||
side, this is almost like an automatically generated keeper method and could maybe be used intead of keepers eventually
|
||||
(see [\#7093](https://github.com/cosmos/cosmos-sdk/issues/7093)):
|
||||
|
||||
```go
|
||||
package gov
|
||||
|
||||
type MsgServer interface {
|
||||
SubmitProposal(context.Context, *MsgSubmitProposal) (*MsgSubmitProposalResponse, error)
|
||||
}
|
||||
```
|
||||
|
||||
On the client side, developers could take advantage of this by creating RPC implementations that encapsulate transaction
|
||||
logic. Protobuf libraries that use asynchronous callbacks, like [protobuf.js](https://github.com/protobufjs/protobuf.js#using-services)
|
||||
could use this to register callbacks for specific messages even for transactions that include multiple `Msg`s.
|
||||
|
||||
For backwards compatibility, existing `Msg` types should be used as the request parameter
|
||||
for `service` definitions. Newer `Msg` types which only support `service` definitions
|
||||
should use the more canonical `Msg...Request` names.
|
||||
|
||||
### Encoding
|
||||
|
||||
Currently, we are encoding `Msg`s as `Any` in `Tx`s which involves packing the
|
||||
binary-encoded `Msg` with its type URL.
|
||||
|
||||
The type URL for `MsgSubmitProposal` based on the proto3 spec is `/cosmos.gov.MsgSubmitProposal`.
|
||||
|
||||
The fully-qualified name for the `SubmitProposal` service method above (also
|
||||
based on the proto3 and gRPC specs) is `/cosmos.gov.Msg/SubmitProposal` which varies
|
||||
by a single `/` character. The generated `.pb.go` files for protobuf `service`s
|
||||
include names of this form and any compliant protobuf/gRPC code generator will
|
||||
generate the same name.
|
||||
|
||||
In order to encode service methods in transactions, we encode them as `Any`s in
|
||||
the same `TxBody.messages` field as other `Msg`s. We simply set `Any.type_url`
|
||||
to the full-qualified method name (ex. `/cosmos.gov.Msg/SubmitProposal`) and
|
||||
set `Any.value` to the protobuf encoding of the request message
|
||||
(`MsgSubmitProposal` in this case).
|
||||
|
||||
### Decoding
|
||||
|
||||
When decoding, `TxBody.UnpackInterfaces` will need a special case
|
||||
to detect if `Any` type URLs match the service method format (ex. `/cosmos.gov.Msg/SubmitProposal`)
|
||||
by checking for two `/` characters. Messages that are method names plus request parameters
|
||||
instead of a normal `Any` messages will get unpacked into the `ServiceMsg` struct:
|
||||
|
||||
```go
|
||||
type ServiceMsg struct {
|
||||
// MethodName is the fully-qualified service name
|
||||
MethodName string
|
||||
// Request is the request payload
|
||||
Request MsgRequest
|
||||
}
|
||||
```
|
||||
|
||||
### Routing
|
||||
|
||||
In the future, `service` definitions may become the primary method for defining
|
||||
`Msg`s. As a starting point, we need to integrate with the SDK's existing routing
|
||||
and `Msg` interface.
|
||||
|
||||
To do this, `ServiceMsg` implements the `sdk.Msg` interface and its handler does the
|
||||
actual method routing, allowing this feature to be added incrementally on top of
|
||||
existing functionality.
|
||||
|
||||
### `MsgRequest` interface
|
||||
|
||||
All request messages will need to implement the `MsgRequest` interface which is a
|
||||
simplified version of `Msg`, without `Route()`, `Type()` and `GetSignBytes()` which
|
||||
are no longer needed:
|
||||
|
||||
```go
|
||||
type MsgRequest interface {
|
||||
proto.Message
|
||||
ValidateBasic() error
|
||||
GetSigners() []AccAddress
|
||||
}
|
||||
```
|
||||
|
||||
`ServiceMsg` will forward its `ValidateBasic` and `GetSigners` methods to the `MsgRequest`
|
||||
methods.
|
||||
|
||||
### Module Configuration
|
||||
|
||||
In [ADR 021](./adr-021-protobuf-query-encoding.md), we introduced a method `RegisterQueryService`
|
||||
to `AppModule` which allows for modules to register gRPC queriers.
|
||||
|
||||
To register `Msg` services, we attempt a more extensible approach by converting `RegisterQueryService`
|
||||
to a more generic `RegisterServices` method:
|
||||
|
||||
```go
|
||||
type AppModule interface {
|
||||
RegisterServices(Configurator)
|
||||
...
|
||||
}
|
||||
|
||||
type Configurator interface {
|
||||
QueryServer() grpc.Server
|
||||
MsgServer() grpc.Server
|
||||
}
|
||||
|
||||
// example module:
|
||||
func (am AppModule) RegisterServices(cfg Configurator) {
|
||||
types.RegisterQueryServer(cfg.QueryServer(), keeper)
|
||||
types.RegisterMsgServer(cfg.MsgServer(), keeper)
|
||||
}
|
||||
```
|
||||
|
||||
The `RegisterServices` method and the `Configurator` interface are intended to
|
||||
evolve to satisfy the use cases discussed in [\#7093](https://github.com/cosmos/cosmos-sdk/issues/7093)
|
||||
and [\#7122](https://github.com/cosmos/cosmos-sdk/issues/7421).
|
||||
|
||||
When `Msg` services are registered, the framework _should_ verify that all `Msg...Request` types
|
||||
implement the `MsgRequest` interface described above and throw an error during initialization rather
|
||||
than later when transactions are processed.
|
||||
|
||||
### `Msg` Service Implementation
|
||||
|
||||
Just like query services, `Msg` service methods can retrieve the `sdk.Context`
|
||||
from the `context.Context` parameter method using the `sdk.UnwrapSDKContext`
|
||||
method:
|
||||
|
||||
```go
|
||||
package gov
|
||||
|
||||
func (k Keeper) SubmitProposal(goCtx context.Context, params *types.MsgSubmitProposal) (*MsgSubmitProposalResponse, error) {
|
||||
ctx := sdk.UnwrapSDKContext(goCtx)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The `sdk.Context` should have an `EventManager` already attached by the `ServiceMsg`
|
||||
router.
|
||||
|
||||
Separate handler definition is no longer needed with this approach.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Pros
|
||||
- communicates return type clearly
|
||||
- manual handler registration and return type marshaling is no longer needed, just implement the interface and register it
|
||||
- some keeper code could be automatically generate, this would improve the UX of [\#7093](https://github.com/cosmos/cosmos-sdk/issues/7093) approach (1) if we chose to adopt that
|
||||
- generated client code could be useful for clients
|
||||
|
||||
### Cons
|
||||
- supporting both this and the current concrete `Msg` type approach simultaneously could be confusing
|
||||
(we could choose to deprecate the current approach)
|
||||
- using `service` definitions outside the context of gRPC could be confusing (but doesn’t violate the proto3 spec)
|
||||
|
||||
## References
|
||||
|
||||
- [Initial Github Issue \#7122](https://github.com/cosmos/cosmos-sdk/issues/7122)
|
||||
- [proto 3 Language Guide: Defining Services](https://developers.google.com/protocol-buffers/docs/proto3#services)
|
||||
- [Initial pre-`Any` `Msg` designs](https://docs.google.com/document/d/1eEgYgvgZqLE45vETjhwIw4VOqK-5hwQtZtjVbiXnIGc)
|
||||
- [ADR 020](./adr-020-protobuf-transaction-encoding.md)
|
||||
- [ADR 021](./adr-021-protobuf-query-encoding.md)
|
||||
@@ -0,0 +1,319 @@
|
||||
# ADR 032: Typed Events
|
||||
|
||||
## Changelog
|
||||
|
||||
- 28-Sept-2020: Initial Draft
|
||||
|
||||
## Authors
|
||||
|
||||
- Anil Kumar (@anilcse)
|
||||
- Jack Zampolin (@jackzampolin)
|
||||
- Adam Bozanich (@boz)
|
||||
|
||||
## Status
|
||||
|
||||
Proposed
|
||||
|
||||
## Abstract
|
||||
|
||||
Currently in the SDK, events are defined in the handlers for each message as well as `BeginBlock` and `EndBlock`. Each module doesn't have types defined for each event, they are implemented as `map[string]string`. Above all else this makes these events difficult to consume as it requires a great deal of raw string matching and parsing. This proposal focuses on updating the events to use **typed events** defined in each module such that emiting and subscribing to events will be much easier. This workflow comes from the experience of the Akash Network team.
|
||||
|
||||
## Context
|
||||
|
||||
Currently in the SDK, events are defined in the handlers for each message, meaning each module doesn't have a cannonical set of types for each event. Above all else this makes these events difficult to consume as it requires a great deal of raw string matching and parsing. This proposal focuses on updating the events to use **typed events** defined in each module such that emiting and subscribing to events will be much easier. This workflow comes from the experience of the Akash Network team.
|
||||
|
||||
[Our platform](http://github.com/ovrclk/akash) requires a number of programatic on chain interactions both on the provider (datacenter - to bid on new orders and listen for leases created) and user (application developer - to send the app manifest to the provider) side. In addition the Akash team is now maintaining the IBC [`relayer`](https://github.com/ovrclk/relayer), another very event driven process. In working on these core pieces of infrastructure, and integrating lessons learned from Kubernetes developement, our team has developed a standard method for defining and consuming typed events in SDK modules. We have found that it is extremely useful in building this type of event driven application.
|
||||
|
||||
As the SDK gets used more extensively for apps like `peggy`, other peg zones, IBC, DeFi, etc... there will be an exploding demand for event driven applications to support new features desired by users. We propose upstreaming our findings into the SDK to enable all SDK applications to quickly and easily build event driven apps to aid their core application. Wallets, exchanges, explorers, and defi protocols all stand to benefit from this work.
|
||||
|
||||
If this proposal is accepted, users will be able to build event driven SDK apps in go by just writing `EventHandler`s for their specific event types and passing them to `EventEmitters` that are defined in the SDK.
|
||||
|
||||
The end of this proposal contains a detailed example of how to consume events after this refactor.
|
||||
|
||||
This proposal is specifically about how to consume these events as a client of the blockchain, not for intermodule communication.
|
||||
|
||||
## Decision
|
||||
|
||||
__Step-1__: Implement additional functionality in the `types` package: `EmitTypedEvent` and `ParseTypedEvent` functions
|
||||
|
||||
```go
|
||||
// types/events.go
|
||||
|
||||
// EmitTypedEvent takes typed event and emits converting it into sdk.Event
|
||||
func (em *EventManager) EmitTypedEvent(event proto.Message) error {
|
||||
evtType := proto.MessageName(event)
|
||||
evtJSON, err := codec.ProtoMarshalJSON(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var attrMap map[string]json.RawMessage
|
||||
err = json.Unmarshal(evtJSON, &attrMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var attrs []abci.EventAttribute
|
||||
for k, v := range attrMap {
|
||||
attrs = append(attrs, abci.EventAttribute{
|
||||
Key: []byte(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
|
||||
em.EmitEvent(Event{
|
||||
Type: evtType,
|
||||
Attributes: attrs,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseTypedEvent converts abci.Event back to typed event
|
||||
func ParseTypedEvent(event abci.Event) (proto.Message, error) {
|
||||
concreteGoType := proto.MessageType(event.Type)
|
||||
if concreteGoType == nil {
|
||||
return nil, fmt.Errorf("failed to retrieve the message of type %q", event.Type)
|
||||
}
|
||||
|
||||
var value reflect.Value
|
||||
if concreteGoType.Kind() == reflect.Ptr {
|
||||
value = reflect.New(concreteGoType.Elem())
|
||||
} else {
|
||||
value = reflect.Zero(concreteGoType)
|
||||
}
|
||||
|
||||
protoMsg, ok := value.Interface().(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q does not implement proto.Message", event.Type)
|
||||
}
|
||||
|
||||
attrMap := make(map[string]json.RawMessage)
|
||||
for _, attr := range event.Attributes {
|
||||
attrMap[string(attr.Key)] = attr.Value
|
||||
}
|
||||
|
||||
attrBytes, err := json.Marshal(attrMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = jsonpb.Unmarshal(strings.NewReader(string(attrBytes)), protoMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return protoMsg, nil
|
||||
}
|
||||
```
|
||||
|
||||
Here, the `EmitTypedEvent` is a method on `EventManager` which takes typed event as input and apply json serialization on it. Then it maps the JSON key/value pairs to `event.Attributes` and emits it in form of `sdk.Event`. `Event.Type` will be the type URL of the proto message.
|
||||
|
||||
When we subscribe to emitted events on the tendermint websocket, they are emitted in the form of an `abci.Event`. `ParseTypedEvent` parses the event back to it's original proto message.
|
||||
|
||||
__Step-2__: Add proto definitions for typed events for msgs in each module:
|
||||
|
||||
For example, let's take `MsgSubmitProposal` of `gov` module and implement this event's type.
|
||||
|
||||
```protobuf
|
||||
// proto/cosmos/gov/v1beta1/gov.proto
|
||||
// Add typed event definition
|
||||
|
||||
package cosmos.gov.v1beta1;
|
||||
|
||||
message EventSubmitProposal {
|
||||
string from_address = 1;
|
||||
uint64 proposal_id = 2;
|
||||
TextProposal proposal = 3;
|
||||
}
|
||||
```
|
||||
|
||||
__Step-3__: Refactor event emission to use the typed event created and emit using `sdk.EmitTypedEvent`:
|
||||
|
||||
```go
|
||||
// x/gov/handler.go
|
||||
func handleMsgSubmitProposal(ctx sdk.Context, keeper keeper.Keeper, msg types.MsgSubmitProposalI) (*sdk.Result, error) {
|
||||
...
|
||||
types.Context.EventManager().EmitTypedEvent(
|
||||
&EventSubmitProposal{
|
||||
FromAddress: fromAddress,
|
||||
ProposalId: id,
|
||||
Proposal: proposal,
|
||||
},
|
||||
)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### How to subscribe to these typed events in `Client`
|
||||
|
||||
> NOTE: Full code example below
|
||||
|
||||
Users will be able to subscribe using `client.Context.Client.Subscribe` and consume events which are emitted using `EventHandler`s.
|
||||
|
||||
Akash Network has built a simple [`pubsub`](https://github.com/ovrclk/akash/blob/90d258caeb933b611d575355b8df281208a214f8/pubsub/bus.go#L20). This can be used to subscribe to `abci.Events` and [publish](https://github.com/ovrclk/akash/blob/90d258caeb933b611d575355b8df281208a214f8/events/publish.go#L21) them as typed events.
|
||||
|
||||
Please see the below code sample for more detail on this flow looks for clients.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
* Improves consistency of implementation for the events currently in the sdk
|
||||
* Provides a much more ergonomic way to handle events and facilitates writing event driven applications
|
||||
* This implementation will support a middleware ecosystem of `EventHandler`s
|
||||
|
||||
### Negative
|
||||
|
||||
|
||||
## Detailed code example of publishing events
|
||||
|
||||
This ADR also proposes adding affordances to emit and consume these events. This way developers will only need to write
|
||||
`EventHandler`s which define the actions they desire to take.
|
||||
|
||||
```go
|
||||
// EventEmitter is a type that describes event emitter functions
|
||||
// This should be defined in `types/events.go`
|
||||
type EventEmitter func(context.Context, client.Context, ...EventHandler) error
|
||||
|
||||
// EventHandler is a type of function that handles events coming out of the event bus
|
||||
// This should be defined in `types/events.go`
|
||||
type EventHandler func(proto.Message) error
|
||||
|
||||
// Sample use of the functions below
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
if err := TxEmitter(ctx, client.Context{}.WithNodeURI("tcp://localhost:26657"), SubmitProposalEventHandler); err != nil {
|
||||
cancel()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SubmitProposalEventHandler is an example of an event handler that prints proposal details
|
||||
// when any EventSubmitProposal is emitted.
|
||||
func SubmitProposalEventHandler(ev proto.Message) (err error) {
|
||||
switch event := ev.(type) {
|
||||
// Handle governance proposal events creation events
|
||||
case govtypes.EventSubmitProposal:
|
||||
// Users define business logic here e.g.
|
||||
fmt.Println(ev.FromAddress, ev.ProposalId, ev.Proposal)
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TxEmitter is an example of an event emitter that emits just transaction events. This can and
|
||||
// should be implemented somewhere in the SDK. The SDK can include an EventEmitters for tm.event='Tx'
|
||||
// and/or tm.event='NewBlock' (the new block events may contain typed events)
|
||||
func TxEmitter(ctx context.Context, cliCtx client.Context, ehs ...EventHandler) (err error) {
|
||||
// Instantiate and start tendermint RPC client
|
||||
client, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = client.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the pubsub bus
|
||||
bus := pubsub.NewBus()
|
||||
defer bus.Close()
|
||||
|
||||
// Initialize a new error group
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
// Publish chain events to the pubsub bus
|
||||
eg.Go(func() error {
|
||||
return PublishChainTxEvents(ctx, client, bus, simapp.ModuleBasics)
|
||||
})
|
||||
|
||||
// Subscribe to the bus events
|
||||
subscriber, err := bus.Subscribe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle all the events coming out of the bus
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-subscriber.Done():
|
||||
return nil
|
||||
case ev := <-subscriber.Events():
|
||||
for _, eh := range ehs {
|
||||
if err = eh(ev); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return group.Wait()
|
||||
}
|
||||
|
||||
// PublishChainTxEvents events using tmclient. Waits on context shutdown signals to exit.
|
||||
func PublishChainTxEvents(ctx context.Context, client tmclient.EventsClient, bus pubsub.Bus, mb module.BasicManager) (err error) {
|
||||
// Subscribe to transaction events
|
||||
txch, err := client.Subscribe(ctx, "txevents", "tm.event='Tx'", 100)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unsubscribe from transaction events on function exit
|
||||
defer func() {
|
||||
err = client.UnsubscribeAll(ctx, "txevents")
|
||||
}()
|
||||
|
||||
// Use errgroup to manage concurrency
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
// Publish transaction events in a goroutine
|
||||
g.Go(func() error {
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break
|
||||
case ed := <-ch:
|
||||
switch evt := ed.Data.(type) {
|
||||
case tmtypes.EventDataTx:
|
||||
if !evt.Result.IsOK() {
|
||||
continue
|
||||
}
|
||||
// range over events, parse them using the basic manager and
|
||||
// send them to the pubsub bus
|
||||
for _, abciEv := range events {
|
||||
typedEvent, err := sdk.ParseTypedEvent(abciEv)
|
||||
if err != nil {
|
||||
return er
|
||||
}
|
||||
if err := bus.Publish(typedEvent); err != nil {
|
||||
bus.Close()
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
// Exit on error or context cancelation
|
||||
return g.Wait()
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
- [Publish Custom Events via a bus](https://github.com/ovrclk/akash/blob/90d258caeb933b611d575355b8df281208a214f8/events/publish.go#L19-L58)
|
||||
- [Consuming the events in `Client`](https://github.com/ovrclk/deploy/blob/bf6c633ab6c68f3026df59efd9982d6ca1bf0561/cmd/event-handlers.go#L57)
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
## Status
|
||||
|
||||
> A decision may be "proposed" if the project stakeholders haven't agreed with it yet, or "accepted" once it is agreed. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement.
|
||||
> {Deprecated|Proposed|Accepted} {Implemented|Not Implemented}
|
||||
{DRAFT | PROPOSED} Not Implemented
|
||||
|
||||
> Please have a look at the [PROCESS](./PROCESS.md#adr-status) page.
|
||||
> Use DRAFT if the ADR is in a draft stage (draft PR) or PROPOSED if it's in review.
|
||||
|
||||
|
||||
## Abstract
|
||||
|
||||
Reference in New Issue
Block a user