-
Notifications
You must be signed in to change notification settings - Fork 320
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
CIP-0078? | Extended Local Chain Sync Protocol #375
Conversation
I believe implementing this CIP would solve: IntersectMBO/cardano-node#3691 |
cip-ledger-state.md
Outdated
|
||
The ledger state data that would provided over the extended chain sync protocol is limited to: | ||
|
||
* Per epoch stake distribution (tickle fed in a deterministic manner). |
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.
Should we specify that the user may wish to request one of the three (labeled mark
, set
, go
) snap shots? Is the live, up-to-date distribution ever needed (though note that this one changes block by block)?
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 think yes. It is very useful to request only 1 of mark, set, go. @papacarp at pooltool needs mark
. At dripdropz, I need just the go
snapshot.
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.
db-sync extracts slices of ~2000 per block from the set
snashot starting from the epoch boundary. The size may be bigger based on the expected number of blocks in the epoch.
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.
For the sake of simpilicity, I really think this should be push only from the node
to the consumer..
The live up-to-date distribution is not currently needed by db-sync
.
As for which one (and making sure I have this correct, mark
is the stake distributions that will be used in current_epoch + 2
, set
in current_epoch + 1
and set
in the current_epoch
). My understanding is that set
is not guaranteed stable in the first part of an epoch, but I would suggest this is the one we want, but only as soon as it is stable.
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 need both mark and set in mithril for example. Different use cases will require different sets so providing all of them would just make things simpler because AFAIK they are maintained together in the Ledger?
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.
As for which one (and making sure I have this correct,
mark
is the stake distributions that will be used incurrent_epoch + 2
,set
incurrent_epoch + 1
andset
in thecurrent_epoch
). My understanding is thatset
is not guaranteed stable in the first part of an epoch, but I would suggest this is the one we want, but only as soon as it is stable.
Slight correction here:
mark = current_epoch + 1 (this is next epochs stake distribution)
set = current_epoch (this is the stake distribution being used CURRENTLY to make blocks)
go = current_epoch - 1 (this is used for reward calculations)
mark snapshot would not be guaranteed until after the stability window I presume but it would be good to pull whenever the consumer wants as each application will have different accuracy requirements.
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.
Sorry, @papacarp, about half the time I look at the mark|set|go
terminology I get it wrong :/.
cip-ledger-state.md
Outdated
The ledger state data that would provided over the extended chain sync protocol is limited to: | ||
|
||
* Per epoch stake distribution (tickle fed in a deterministic manner). | ||
* Per epoch rewards (tickle fed in a deterministic manner). |
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.
Should we specify an event on the epoch boundary that lists the stake credentials which appeared in the reward calculation, but were de-registered when the reward were given out?
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'm not sure on this one. @erikd would know if db-sync needs it in that format.
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.
Should we specify an event on the epoch boundary that lists the stake credentials which appeared in the reward calculation, but were de-registered when the reward were given out?
I believe this exists already. It's called RestrainedRewards
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.
Currently, @erikd and @kderme depend on the ledger events, and indeed they do use an event to sort this problem out: https://github.com/input-output-hk/cardano-ledger/blob/fd806798eb68e904435720dbcea47def27c1a0fc/eras/shelley/impl/src/Cardano/Ledger/Shelley/Rules/NewEpoch.hs#L264-L267
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.
Yes, there currently is an event like this and we need to keep it.
Remember that the node does not store old ledger states, nor ledger events. Thus it cannot provide information from the ledger state for chain sync clients that need to start at the beginning of the chain. In principle it could only provide such information for the recent parts of the chain (and could only do so at the cost of considerable complexity in the node). |
A possible solution is to extend the ChainDB with a key-value db, where key is the block hash and value is a binary blob of the ledger events. A new pair will be inserted for each applied block and the extended chainsync server will query the db and append the events to the block. Rollbacks would cause a pair delete. Maintaining the db would be optional, so it won't affect normal operation for SPOs/wallets. Of course it would add complexity in the code. I guess another challenge is to reach consensus on the exact content and format of events. |
The content and format of ledger events is very much not stable. Such a table would need to be reconstructed on every node upgrade, whenever the ledger events change. Then there's the size of such a table. It'll be big. Again this all adds complexity. The alternative is that the Cardano API makes it convenient to manage the ledger state client side. For resource use, the solution is that the Cardano API eventually migrates to use the same on-disk ledger state storage scheme as the node itself uses (once that functionality is stable within the node). |
The size of ledger state already makes it highly inconvenient and |
Providing the events as they are produced, without storing them, could be a relatively simple first step: The events are already emitted by the ledger. This won't solve all the problems and would still require some mechanism to prime db-sync when it starts from scratch. To address this latter point I have suggested writing some tool, outside of db-sync, that could populate it from and existing stored ledger state. Then db-sync would be able to catch-up with the events stream from this point. Note that this could require restarting the node from that specific point in time to ensure events are correctly emitted. Another snag to take care of are rollbacks: I don't know how ledger currently handle events, and whether or not those events are invertible. If this were the case then rollbacks could be handled very gracefully by any events client by un-applying the events. Otherwise, extra care would be needed within db-sync or any client. |
That's definitely desirable, eg. to have some tool or family of tools able to work with the node's raw storage and particularly ledger state, and this would be useful as suggested in my previous comment as a way to prime db-sync or any other client. |
cip-ledger-state.md
Outdated
* Current protocol parameters. | ||
|
||
The first of these ledger state components is by far the largest component and is probably not | ||
needed outside the node (and definitely not needed by db-sync. However the others are needed and |
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.
typo: missing closing paren
cip-ledger-state.md
Outdated
|
||
The ledger state data that would provided over the extended chain sync protocol is limited to: | ||
|
||
* Per epoch stake distribution (tickle fed in a deterministic manner). |
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 need both mark and set in mithril for example. Different use cases will require different sets so providing all of them would just make things simpler because AFAIK they are maintained together in the Ledger?
This means that when `node` and `db-sync` are run on the same machine (which is the recommended | ||
configuration) that machine has two basically identical copies of ledger state. Ledger state is a | ||
*HUGE* data structure and the mainnet version currently consumes over 10 Gigabytes of memory. | ||
Furthermore, maintaining ledger state duplicates code functionality that is in `ouroboros-consensus` |
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.
An alternative solution that won't solve the duplicate use of RAM but would solve the duplicate use of code would be to pack consensus + ledger into a "Slim node" that works in read-only mode with an existing ChainDB, that would be packaged and built along side the node.
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.
Then db-sync or any client could use that "ghost node" to replay blocks from any point in time, generate events, query ledger state and what not. I am even surprised such a tool does not already exist: Is this not somewhat akin to what db-analyser do?
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.
Can't this idea be extended even more to solve the duplicate RAM problem? What if db-sync maintained its own ChainDB, connected to peers itself, ran ouroboros etc? That way it doesn't need a separate trusted node process, there is no replication, no need for protocol extension, deployment is greatly simplified etc. At the same time this adds no complexity to the node that @dcoutts understandably wants to avoid, as this complexity will live in the db-sync repository.
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.
Actually, this is an idea that popped into my head recently: Make the full node's logic available as a library instead of only as an executable. Then db-sync could just embed the node's logic, and provided there's some way to plug "decorators" here and there, could just tap on node processing stuff, or poke at the current ledger state when it needs, etc...
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.
Another alternative -- and straightforward -- solution would be to also simply "dump" information required to the calculation of implicit stuff (such as rewards) into some file as the node processes them. And either, document the format of that file or provide a thin api-layer to query its content.
Having just that would solve many problems down the line; client applications will almost always be connected to a local node and that node will have to do this work anyway. So instead of duplicating that work; simply record the information when its done and gives way to access that information later on. It could be a mere .csv or .json file recording the stake distribution on each epoch transition.
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.
Oh, ok, so you would not need to run a node on a machine running db-sync
at all. Not sure how this compares to mu proposal in terms of amount of work or complexity.
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.
It's probably (much) more work so I'd not consider this as an alternative for this particular CIP
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.
Funny, in the last company I worked for, everything was written as a library first (makes testing much easier) and then thin wrapper was added to make it into an executable.
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.
Then db-sync or any client could use that "ghost node" to replay blocks from any point in time, generate events, query ledger state and what not. I am even surprised such a tool does not already exist: Is this not somewhat akin to what db-analyser do?
Is this similar to what Santiago from TxPipe proposed a while back called Dolos, a Data Node?
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 don't know, I wasn't there a while back ;) But I guess this idea is pretty much obvious and not novel so I am pretty sure other people have thought of iti.
|
||
|
||
|
||
## Implementations |
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.
A possible implementation first step would be to write a prototype funnelling existing events (or only one of them for simplicity's sake because I assume there would be need to implement serialisation) through the consensus code down to the network stack, in order to properly understand the impact of this change.
Is this about reusing the same utxo db that the node uses by client processes through an extended api or about replicating the utxo-db for the client? |
Note there already exist tools that "decrypt" the ledger state and can interact with a DB: https://github.com/input-output-hk/cardano-ledger/tree/master/libs/ledger-state |
@kderme I mean the Cardano API reusing the same underlying library to manage the ledger state on disk. It could not reuse the same instance of the on-disk state, simply because that state only corresponds to the end of the chain, but the chain sync protocol allows syncing from the beginning. |
Right, integrating and replicating utxo-hd on the client side would reduce memory but it would be very heavy on IOPS requirements. Running on a vm 2 x utxo-hd instances plus a very IO heavy Postgres for db-sync is not really ideal. UTXO-hd is still great and will benefit db-sync users even without integrating it, by cutting total memory usage almost in half. API can definitely provide convenience, but it cannot reduce recourses usage. This would require changes on the protocol level. |
Yes, fundamentally the ledger state either has to be in memory or on disk, and the latter costs IOPS. And given that you need to be able to sync from genesis, then that involves two copies of the ledger state somewhere (whether it's client or server side). The idea to only use ledger events and never directly access the ledger state is interesting, but it's still quite unstable (so would require replaying the chain and ledger state on every upgrade) and we don't yet have any real idea of how big the ledger events will be. We know it's big because it at least includes all of the reward payouts for every stake address every epoch. |
The Marconi indexer[1] would also benefit from having (parts of) ledger state available, and as outlined in the CIP it would be beneficial not to have two identical ledger states living in memory. Getting ledger state from the node would save any indexer needing to compute it on the client side. From the comments above, an ideal situation would be what @abailly-iohk and @kderme said:
I.e have db-sync, Marconi or any other blockchain indexer act as a passive node which could discover peers and synchronize similar to how the node does it now. To do this node would need to be a library that provided the code to implement it (the API being perhaps a stream of blocks and a fold function over it that would give ledger state at every block). For now, the short term approach for Marconi for indexers that require info from ledger state is to reconstruct it on the client side -- much like dbsync does, and with the same downsides. As an aside, the blockhain and folding over it into some desirable data structure sounds much like event sourcing, in event sourcing's terms ledger state would be called a "read model". [1] Indexer is any program that picks out from the blockchain any info that a dApp developer might care about and filters, folds and/or stores it. |
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.
While all the other technical reviews are being addressed, I would happily see this promoted to a Candidate CIP especially because of the promise of a long awaited answer to IntersectMBO/cardano-node#3691 (as already mentioned).
and periodically extracting the parts required. | ||
|
||
This means that when `node` and `db-sync` are run on the same machine (which is the recommended | ||
configuration) that machine has two basically identical copies of ledger state. Ledger state is a |
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 think there's little use-case for the UTxO state or the protocol parameters because these are straightforward to obtain from the chain-sync protocol itself.
However, anything that regards rewards is indeed a pain in the *** to work with; because rewards are implicit in the protocol. I'd love to see a quick justification here (instead of "basically" 😅 ...) stating just that. There's no way for a client application to provide reliable information on rewards without maintaining the entire ledger state; for one needs to know what everyone owns in order to calculate rewards snapshots on each era.
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 am pretty sure that the protocol parameters are not available on chain, only the voting for the changes. Yes, the ledger rules can be applied to whatever voting is seen on chain but that is way more fragile than getting the parameters from the source.
Agree, that nobody in their right mind would want the UTxO state.
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 protocol parameters are available on chain, NewEpochState
-> EpochState
-> PParams
.
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.
It would be really nice to have a function to pull the protocol parameters out of ledger state so that I did not have to go digging around inside it.
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.
you're in luck, I have one right here: nesEs . esPp
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 would call that "digging into the internals".
I am asking for an officially maintained function that is part of an official API.
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 lack of a quality api is indeed an issue, but a much bigger issues imo, is the need to replicate resources outside the node, especially for ledger state data. An api cannot help there, this requires improvements on the protocol level. Api and protocol are two different topics.
This means that when `node` and `db-sync` are run on the same machine (which is the recommended | ||
configuration) that machine has two basically identical copies of ledger state. Ledger state is a | ||
*HUGE* data structure and the mainnet version currently consumes over 10 Gigabytes of memory. | ||
Furthermore, maintaining ledger state duplicates code functionality that is in `ouroboros-consensus` |
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.
Another alternative -- and straightforward -- solution would be to also simply "dump" information required to the calculation of implicit stuff (such as rewards) into some file as the node processes them. And either, document the format of that file or provide a thin api-layer to query its content.
Having just that would solve many problems down the line; client applications will almost always be connected to a local node and that node will have to do this work anyway. So instead of duplicating that work; simply record the information when its done and gives way to access that information later on. It could be a mere .csv or .json file recording the stake distribution on each epoch transition.
|
||
The proposed solution is an enhanced local chain sync protocol that would only be served over a | ||
local domain socket. The enhanced chain chain sync protocol would include the existing block chain | ||
data as well as events containing the ledger data that db-sync needs. This enhanced local chain |
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.
This has always been said to be "impossible" because the ledger does not record past events and wouldn't be able to replay any of those events that are too far in the past.
Wouldn't it make sense to start by recording the events somewhere?
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.
That is effectively part of this proposal.
JSON. | ||
|
||
This enhanced local chain sync protocol is basically the data that would be provided by the | ||
proposed Scientia program (which as far as I am aware has been dropped). |
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.
What is this "Scientia program" about?
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.
It was a proposal put forward by John Woods during his temporary tenure at IOG.
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.
@KtorZ The output document isn't public, but it was a user research report of what chain indexers exist, which and why people use, pros, cons etc.
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.
If it isn't public, I don't see why it is mentioned here 👍
marked as |
Co-authored-by: Robert Phair <[email protected]>
Co-authored-by: Robert Phair <[email protected]>
Co-authored-by: Matthias Benkort <[email protected]>
As discussed in today's editor meeting:
|
Perhaps we can have a quick discussion with @scarmuega and @erikd to clarify potential solution(s) and see whether or not it would make sense to transform this CIP in a CPS? There's a relatively straightforward implementation path that implies making the existing |
After going through the whole thread, I must say that I agree with the motivation of the CIP (or CPS) and with the overall theme of moving to a more event-driven architecture. IMHO, client-side data integrations should be implemented exclusively using the event-sourcing pattern. In the context of this pattern, the term "command" refers to a request or instruction to perform a specific action. In contrast, an "event" is a record of something that has already happened. Integration between components of this architecture happens by sharing "events" (not commands). In Cardano, we have this tradition of building data integration clients by parsing and re-playing commands (blocks / txs), assuming that the end-result will match the correct view of the ledger. This is only possible given the deterministic nature of Cardano, but is very inefficient and prone to errors since it requires re-implementing a lot of the domain logic. I believe that downstream components should rely on a stream of structured domain events that convey the result of applying blocks to the ledger. AFAIK, this is what @abailly-iohk is proposing. I know that Oura and Scrolls could be drastically simplified by using this approach, I assume that other tools such as DB-Sync, Carp, Ogmios, Kupo, etc would benefit in a similar fashion. Eager to learn, but I can't think of any specific client-side use case that benefits from having the raw cbor block as data-source (as opposed to a stream of structured domain events). Regarding implementation strategies, there're many valid options with different levels of efforts attached to it. There's some overlap with the effort that TxPipe is doing for Dolos. This project is still on an early stage, we could revisit the scope and see how to align it with the goals described by this PR. Happy to join a meeting for further discussion. |
Please also consider what I wrote just above as well. :) (here, not in the comment in this thread) |
@scarmuega This might sound very haskell-specific, but through local chain-sync you can easily get parsed blocks, not CBOR. For other languages, writing a parser is a problem though. Regarding event-sourcing, the fundamental sequence of events you get are blocks/transactions. If there are other types of info a data integration client needs, then they can map and fold those into the events they need. For resumption (when that is what "business" needs :)), the event-sourcing answer seems to be to occasionally snapshot the data structure that is being folded, and then compute onwards/backwards from them by the use of events (much like I and P frames work in video compression). |
Thanks for chiming in @scarmuega. Yes, what you describe is exactly what I have in mind and I think a desirable future for the node: Have it be as lean as possible, and distribute information to whoever is interested in it, in both forms: The commands (blocks, txs) and the Events (change to UTxO set, to SD, rewards, ...). |
Agreed, we could consider the parsing issue as a nice-to-have, but given that we're talking about client-side integration, we should not be constrained by the hard networking requirements of the node-to-node protocol and maybe provide a developer-friendly interface. In fact, this is what Ogmios does, which IMO is what the node should provide by default.
This is the core of my argument. The folding is already happening at the node. The "data" available as a result of this folding is much richer than the Block / Tx in itself. For example, a domain event representing an already folded Tx could:
Stake reward calculation is another topic that could be improved. AFAIK (happy to be corrected), the results from reward calculations at the epoch boundary could also be exposed as events, something that is not easily accesible at the moment.
Nice analogy (fun fact, my last job involved working with video compression for AI). My argument is that we're trying to improve the last mile integration (Node-to-Client). Following your analogy, I see it as the HDMI cable that transports decoded video ready for consumption. Of course, all of the above can be computed from blocks / txs, but it adds an unnecessary burden on the client side. |
@eyeinsky This is a heavily overloaded terminology but I think that @scarmuega is right: From the point of the view of the Ledger and its state, blocks are actually commands and events are whatever gets changed in the ledger from applying those commands. Of course, from the point of view of the consensus, blocks and txs are more events because they represent "past events" that happened (eg. there's been a consensus on them). |
One important aspect to consider in this design is also that some events being mentioned here cannot generally be "re-generated" after the facts because the ledger only maintains an aggregated view of the chain. So, some events that may be emitted while syncing (like, rewards distribution on a particular epoch) can't necessarily be streamed again on demand from a client. Unless the ledger/consensus start also storing those events (which sounds like doable and like a fairly low hanging fruit). |
If I understand correctly, only blocks can be streamed again from an arbitrary location. To start having a valid ledger state from the middle of the chain means that there needs to be some snapshot of it. Not sure how large a ledger state is to serialize and store, but if storing all of them is too much, then having a periodic snapshot is the way to go. I.e, for getting ledger state (or ledger events) for any given chain point, find the latest snapshot before that, apply blocks until reaching the desired point. I think fold function that makes ledger essentially boils down to a type signature like this: And another info point: both dbsync and foldBlocks (in cardano-api:Cardano.Api.LedgerState) don't actually reimplement the ledger rules but import that functionality from the ledger (e.g here). They do add at thin layer of code though, so mistakes are possible there. |
If you also have the state before that the location.
This is something the node does: It periodically snapshots (2 by default but that's configurable IIRC) the full ledger state so that in case of crashes, it does not have to rebuild the full state from scratch.
Sure, but they need to maintain the full ledger state in memory (or serialise/deserialise it) to be able to |
Not sure what you mean by that? Blocks are stored in the immutableDB and can be fetched back at anytime, provided that clients can provide points on chain corresponding to those blocks. So in principle, there's no need to hold on the ledger state whatsoever to access blocks.
Sounds like the way to go perhaps? Increasing the number of snapshots and enabling client applications to also query from those snapshots? In the end, applying blocks is relatively fast. Even ten thousands blocks take only a couple of seconds to process; so having frequent but sparse snapshots sounds interesting; especially if the snapshot frequency can be tweaked on startup (so that client applications connected to a local node can ask for more snapshots than block producers who probably care less).
Only if they seek to maintain implicit properties of the chain; like rewards (or really anything connected to rewards like the full stake distribution). Applications like kupo can maintain the entire UTxO-set without resorting to storing the ledger state at all (nor does it even maintain the UTxO set in plain memory). And, it can also maintain partial view of the UTxO set and provide access to any transaction metadata; and that is because everything in the UTxO set is the explicit consequence of a block application. There's no need to know what is the current ledger state in order to identify spent inputs or produced outputs. It is however necessary to know that full ledger state to calculate the stake distribution at the end of an epoch and to know how much rewards should be given to each stakeholders. There's no explicit manifestation of a reward distribution in blocks. And this is the root of all problems described in this proposal. Note that for a while, I've been considering doing something a bit silly which is to simply fork the cardano-node and add a little functionality to dump rewards distribution periodically (on each epoch snapshot) into a file on disk. While arguably inelegant, this would allow any application to consume this information without needing to maintain a full ledger state. |
(I hate having discussions in GH PR 🙄 )
Sure but blocks represent changes to the state of the ledger so to understand what's the result of applying a block you need a Ledger state. If you don't have one, you need to replay the entire chain of blocks from genesis.
Yes, I agree. But snapshots are supposed to be "private", eg. the serialisation format is not standardised so it's only useful within the confines of the existing ledger haskell code. BTW, it's already possible to increase the frequency of snapshots through the
Agreed.
Not sure what you mean by implicit properties but if I look at the cardano-ledger specs, I can see (or could see if the link was still up...) there's quite a lot of rules that are defined which I definitely would not want to reimplement partially. Using raw blocks info works for UTxO but not for other stuff like rewards or stake distribution, and possibly other aspects of the ledger I am overlooking.
And why I and others are suggesting this problem should be solved using an Event sourced approach whic nicely complements the command sourced nature of the blockchain,
That's the kind of things a standalone cardano-node-lib would make it easy to do. |
(*) what I mean by 'implicit properties' are elements that can only determined by calculations that requires the full ledger state and for which there's no direct triggers in blocks. The best example being the distribution of rewards. There's absolutely nothing explicitly recorded on the chain that indicates what rewards were sent to what account because rewards aren't carried by transactions. Implicit properties only exists because all ledgers agree on how to calculate them from a state. They aren't the direct consequence of a command, but rather an implicitly agreed-upon information which can only be derived from the entire ledger state. |
With ledger states snapshots the problem still remains though: parsing them means memory is duplicated, since node and clients need a copy. That's why my suggestion was to store ledger events in a dedicated db #375 (comment). Ledger events, documented in https://github.com/input-output-hk/cardano-ledger/blob/master/docs/LedgerEvents.md, include things that are impossible (like historic rewards, stake distribution) or hard to calculate without the ledger state (epoch params, deposits, refunds). They are smaller than the whole ledger state, but still tell the full story. |
That's exactly the definition of command sourcing: What the system persists is a list of commands and the state is constructed by applying the stream of commands on an initial state. This works iff the
They are the direct consequence of interpreting a "command" in certain ways. Note that the same stream of commands could lead to different states given different interpreters.
It's perfectly possible a command works on part of a ledger state. To use your previous example, we could have a Note this contrasts with the situation in event sourcing where, to keep running the same example, the event stored would include some events about how the rewards are changed so that there would not be any need to know the reward rules to compute those rewards. To makes things more complicated, it's perfectly possible to view a stream of events as commands and run them through an interpreter that would for example, compute some "derived property" as you name it from the stream of events/commands, be it the number of bytes modified, or the distribution of time between 2 blocks, etc. |
@erikd @abailly-iohk is this ever going to come through to completion? The related bug @AndrewWestberg points out (#375 (comment)) is still open so I'm assuming at least one reason why this solution might be adopted... unless it's been superseded somehow. In any case we are running through the unattended bulk in the CIP PR queue & so marking this as practically cc @KtorZ @NetWalker108 @eyeinsky @papacarp @kderme @scarmuega |
I would suggest closing this issue |
thanks @abailly-iohk & I guess reasons for closing were originally posted in #375 (comment). @erikd or others: this can be re-opened with any newly posted reason(s) why this requirement & solution are still valid. |
(current CIP draft in branch)