-
Notifications
You must be signed in to change notification settings - Fork 14
Abci refactor #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Abci refactor #163
Conversation
Really looking forward to digging into this first thing in the a.m.! What you've described in the PR description makes sense, from a quick scan, but will give it a through review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The abci app structure looks pretty good. Just some initial comments so far. Please see my last comment about the ABCI concurrency model and the imminent changes to the consensus connection methods. I'm curious how we are going to adapt.
pkg/modules/databases/execution.go
Outdated
// ExecutionResponse is the response from any interaction that modifies state. | ||
type ExecutionResponse struct { | ||
// Fee is the amount of tokens spent on the execution | ||
Fee *big.Int | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like you're using the equivalent type in pkg/tx
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, a bit of code duplication throughout here. This gets to the larger question of transactions/payloads (both their structure, encoding, and where they should go).
// payloadEncoder is the encoder that encodes and decodes payloads | ||
payloadEncoder PayloadEncoder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The payloadEncoder
is not set in the constructor, but I also don't see any impls of PayloadEncoder
yet.
Since you now have DeliverTx
instantiating the typed payload structs, I imagine that the decoder would simply be json.(Un)Marshal. Is that your thinking?
Also, I'm curious when encoding might be done by the abci app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we discussed this in person but just for thoroughness:
I put that there to show that we might not want to use straight-up JSON encoding/decoding (can be hard to make backwards compatible).
As for when encoding needs to be performed: mostly in DeliverTx
. The transaction itself is decoded from the ABCI type, and the payload then decoded from the transaction.
type PayloadValidatorApprove struct { | ||
ValidatorToApprove string | ||
ApprovedBy string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intent to give all these payload fields json:
tags and use json.Unmarshal in the encoder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe; depending on how we decide to implement encoding/decoding. Ethereum's RLP looks pretty good, with two caveats:
- It can't encode signed integers. Not a huge issue, but a little annoying.
- It can't encode maps. We use maps in payloads pretty often (a lot of this should be changed, but there are some times where it is good, like in Extensions).
return abciTypes.ResponseDeliverTx{ | ||
Code: abciTypes.CodeTypeOK, | ||
GasUsed: res.Fee.Int64(), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does cometBFT not need the other fields set, like Data
and Events
? I don't know what it does with this return, but @charithabandi had quite a bit of information set in the other version of the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional. Good to have, Cometbft has an indexer built based on these events' information. For example, if you create deploy, drop, execute, node join, leave events. You can do list all node joins, or list all txs where node1 approved a joinee etc. So, it's a good to have for lookups.
Also you can on the client side subscribe for events based on certain search params. Ex: Subscribe to all Node join events etc
pkg/abci/abci.go
Outdated
ctx := context.Background() | ||
err := a.committer.Commit(context.Background()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ctx
a.commitWaiter.Wait() | ||
a.commitWaiter.Add(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to look into another approach. Multiple waiters may both unblock at the same time and both hit Add(1)
at the same time. (EDIT: Oh, apply in your atomic waiter is async. Hmm) However, I've started to look into how Application
methods are called and what guarantees are provided about their concurrent use. Here are some points about the ABCI concurrency model:
- it's a work in progress: Introduce relaxed ABCI concurrency models cometbft/cometbft#88
- there are 4 "connections" from comet->abci
- the method calls within one of those connections are synchronous
- depending on the local client used (there's an "unsync" version), we get global sync or sync just within the scope of the individual connections
- All four of
BeginBlock
,DeliverTx
,EndBlock
, andCommit
are from the "consensus connection", so synchronous.
Connection State
CometBFT maintains four concurrent ABCI++ connections, namely Consensus Connection, Mempool Connection, Info/Query Connection, and Snapshot Connection. It is common for an application to maintain a distinct copy of the state for each connection, which are synchronized upon Commit calls.
Concurrency
In principle, each of the four ABCI++ connections operates concurrently with one another. This means applications need to ensure access to state is thread safe. Both the default in-process ABCI client and the default Go ABCI server use a global lock to guard the handling of events across all connections, so they are not concurrent at all. This means whether your app is compiled in-process with CometBFT using the NewLocalClient, or run out-of-process using the SocketServer, ABCI messages from all connections are received in sequence, one at a time.
Closely related is that v0.38 introduces a FinalizeBlock's
app method that "replaces the functionality provided previously by the combination of ABCI methods BeginBlock
, DeliverTx
, and EndBlock
. FinalizeBlock
's parameters are an aggregation of those in BeginBlock
, DeliverTx
, and EndBlock
."
- https://github.com/cometbft/cometbft/blob/main/docs/guides/go.md#133-finalizeblock
- abci: implement finalize block tendermint/tendermint#9468
Maybe the three of us can chat about this in case you already have an understanding about all the above.
Modularization LGTM! What happens if a crash occurs after writing the changesets to the WAL and before checkpointing them to the DB? Would you be truncating the current WAL? or append the tx changesets at the end with the replay block(keep in mind the begin and commit writes)? |
If a crash occurs after changesets are written to the wal, it will assume none of them are applied and reapply them when the committer starts up again (before any new blocks are mined). This is because changesets are idempotent (more or less; we can make them idempotent pretty easily). |
I've pushed up a basic implementation of Committable for our SQL Client in I hope this provides a bit more clarity in the design decisions for I still need to think as bit more on the AtomicCommitter API; @jchappelow is it common to use channels in APIs? I'm going to spend a bit more time tonight thinking these things through, we can discuss more tomorrow. |
In Go, yes, if the method relates to an operation happening in another goroutine. The standard library sets an example in the context and time packages. A typical pattern is to provide a channel that is either closed (usually for broadcast) or sent a result or error. The latter case tends to correspond to promises used in other languages, whereby a What's best all depends on what the caller needs to achieve from the result of the async function. When it's just a gate, or some flow control, a channel generally fits the bill and is most expressive. It's really fine with the extra Apply method, but in the current application logic, it's not clear why it needs it. (Does the abci app need to know that there are two steps to end a block, or even that there's a wal? Maybe. Just discussion point for me.) |
Gotcha. There isn't any reason why the caller explicitly needs access, but here's the breakdown (I'm pretty sure we are on the same page, but just to be clear):
I don't see a reason why it couldn't be handled with a channel. |
Just added an implementation for the engine at None of this is bug tested yet, so I'm sure there is quite a bit of bugs. I'm mostly just leaving it there for anyone who is curious. There's still quite a bit in atomic committer that needs to be cleaned up, so I wouldn't take what is there as Gospel. |
Mmm, thanks for restating that. One thing we want is the app hash on commit completion, and the other is to permit/unblock the next block on apply completion. So perhaps a more natural approach than either an Commit(ctx context, onApplied func(error)) (appHash []byte, err error) Really microscopic design nit in the grand scheme, but helpful for me to understand. Thanks for talking through. |
Implemented on the sessions module; I'm now beginning the full refactor, where I will include it in the abci |
I've gone through and implemented most of what needs to be covered in the refactor. There are a few outstanding areas that are not building, as well a a few areas that are building but we need to rethink, but overall it should be enough to unblock work on CometBFT and the validator store. What's changedThis isn't a comprehensive list, but this does cover the highlights of what has changed: No More UsecasesUsecases is getting removed in favor of modules. They are conceptually similar, except modules have a more limited scope than the use cases. The previous No More EntityWe previously had a package
This issue hasn't actually been solved yet, but only sidestepped. I will cover this a bit more later. Prioriting
|
From a technical perspective, there is a lot to like in this refactor.
It demands redoing certain things, most notably to me the ABCI application type, who's job is just to implement the The scope of the refactor gives me some anxiety, but it feels right and I'm already stuttering on a task because I don't want to do it in what I'm already thinking of as the "old framework". The driver + integ test issue is one that I don't really have a handle on, primarily because I have yet to become familiar with the integ tests, but also because I'm just not sure if the delays that we are creating in these integ test are things that an application using one of the Kwil SDKs would really hide from the user, or at least make less unpleasant. The line between public (top pkg) and internal is still fuzzy sometimes to me, but moving focus to the public pkg feels right at this point. In terms of source control and eliminating some uncertainty for us developers, my current thinking is this:
|
d455dcf
to
d71ad02
Compare
9ea07a6
to
b7d0cd7
Compare
d5a3782
to
7135da1
Compare
…ogic regarding usecases, abci, and our public api into pkg created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
7135da1
to
b24cd4a
Compare
Kudos, SonarCloud Quality Gate passed!
|
…ogic regarding usecases, abci, and our public api into pkg (#163) created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
…ogic regarding usecases, abci, and our public api into pkg (#163) created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
…ogic regarding usecases, abci, and our public api into pkg (#163) created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
…ogic regarding usecases, abci, and our public api into pkg (#163) created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
…ogic regarding usecases, abci, and our public api into pkg (#163) created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
…ogic regarding usecases, abci, and our public api into pkg (#163) created an example of how abci can be implemented with various data stores / modules implemented sessions, still need to add tests implemented sql client for atomic comitter added in closing changesets added implementation for engine changed up sessions a bit more, added more unit tests cleaned up kwild config, deleted old sql packages, deleted chain syncer cleaned up pkg/client refactored tx service refactored driver. this is certainly broken now, and should be re-thought. Our old driver was predicated on synchronous calls; the attempt here was pretty dang good, but I dont think we should be including time.Sleep in our driver just to make it mock the old synchronous version started on dependency injection minor changes to database module minor config fix deleted addrebook
Contained in this branch is an example of how we can implement abci. I tried to design this with what @jchappelow discussed with us on Friday. There are a few different considerations here:
Logical separation of functional responsibilities (Usecases / modules)
I have designed a basic separation of concerns for different parts of our application functionality. Namely, our blockchain has two distinctly separate "applications" that do not logically involve each other:
This example separate these into two different modules.
Atomic commits across data stores
Our application needs to ensure atomic commits across data stores across all applications. We have already designed a way of implementing this using a two-phase commit process. There are virtually unlimited number of different data stores that need to be accounted for:
These data stores can be used in any number of modules (for example, the account store is needed in both the validator module and the database module). This design can handle atomic commits across data stores without coupling the modules that they are used in.
Overview
I have broken down the app into 3 different layers:
The ABCI has a couple jobs (more will certainly need to be added, e.g. snapshotting):
The modules implement specific business logic, and can be considered their own "stand-alone apps". For example, the database module handles all deployments/executions, as well as pricing and writing to the account store. The validator module would also be writing to the same account store, as well as to its own validator store.
Sessions run along-side these, and track + persist changesets when appropriate. A lot of sessions still needs to be implemented, but an example is actually implemented in
pkg/engine/session.go
(this example will need to be taken out of the engine and applied to the more generalizedsession
package, but it should convey what needs to occur + how it can be encapsulated for any sort of data store).Where to Look
There are a few new packages that implement this functionality:
pkg/abci
: this implements a basic (not yet functional) ABCI app. We can certainly move this back into the other ABCI app, but I made this separate for now to make it easy to read.pkg/modules
: this contains a fully functionaldatabase
modules. This is the logical equivalent of theDataset Usecase
, but it takes into account what was discussed by Jon on Friday. Unlikeinternal/usecases
, it does not useinternal/entity
; we sort of have a weird thing going on withentity
where virtually everything ininternal/entity
is duplicated somewhere inpkg
. This has been replaced with using the direct package type.pkg/sessions
: this contains the logic for atomic sessions. At the time of writing this, I'm not actually done with this, but I am going to continue working on it tonight. A logical equivalent can be found inpkg/engine/session.go
.What needs to happen
I want us to have a discussion tomorrow once everyone has had a chance to look at this, so that we can decide whether or not it is a structure we want to further pursue. On top of that, there are a few things that we either still need to figure out / implement to get this "working".
pkg/abci/payloads.go
).ValidatorStore
package and aValidators
module.I'm sure I'm missing a few things here, but this should be enough to get us started down the right path. Please be very honest with any feedback you might have.