diff --git a/common/src/types.rs b/common/src/types.rs index 569a0789..22d45012 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -846,7 +846,7 @@ impl Ratio { /// Withdrawal #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Withdrawal { - /// Stake address to withdraw to + /// Stake address to withdraw from pub address: StakeAddress, /// Value to withdraw diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 00000000..dab14e1d --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,12 @@ +# The Architecture of Acropolis + +These pages give a high-level overview of Acropolis, its philosophy and how the modules interact +to become a full Cardano node. + +## Contents + +- [Modularity](modularity.md) - Why and how Acropolis is split into loosely-coupled modules +- Building a node from the ground up: + - [Simple Mithril UTXOs](system-simple-mithril-utxo.md) - The most basic UTXO follower from a Mithril snapshot + - [Mithril and Sync UTXOs](system-simple-mithril-and-sync-utxo.md) - Adding a live network sync + - [Basic ledger](system-bootstrap-and-sync-with-basic-ledger.md) - Adding basic (Shelley-era) ledger state diff --git a/docs/architecture/modularity.md b/docs/architecture/modularity.md new file mode 100644 index 00000000..d8d575d9 --- /dev/null +++ b/docs/architecture/modularity.md @@ -0,0 +1,184 @@ +# The Modularity of Acropolis + +Acropolis is a modular architecture for building nodes, indexers and +other clients for the Cardano blockchain, and maybe other blockchains +in the future. Compared to other node implementations, it is this +modularity that is its defining feature - but what do we mean by this? + +## Defining modularity + +In some sense, any well-written software is modular; it may be +comprised of functions or classes, and these may be grouped together +in libraries. With use of interfaces or traits, you can make it +somewhat 'pluggable', allowing different implementations to be +switched in. + +The modularity we talk about in Acropolis, though, is a level above +this, and comes from the world of audio-visual processing, +synthesisers, vehicle, marine and aircraft electronics, and enterprise +back-office systems. These might seem widely different fields, but +they share a common philosophy - systems are composed of modules which +do a defined job with defined inputs and outputs, but have no +knowledge of each other - any dependencies between them are only +created when they are configured or plugged together. + +The communication between the modules is done through messages, rather +than conventional function or method calls. In a modular synthesiser, +this 'message' may just be the voltage on a wire; in marine +electronics a 'sentence' on a serial bus; in enterprise software, a +JSON document. In each case they are not usually requests for +something to happen but a statement of some fact - a key has been +pressed, the speed is 4.6 knots, an order has been placed... + +## Publish-subscribe + +In software, you can directly connect modules together (this is usual +in video pipelines, for example), but in enterprise software - and +also modular electronics - it's usual to use some kind of broadcast +mechanism to remove any dependency between modules. Sending every message +to every module works fine for ship data, but in software architecture the +distribution is usually mediated with a "publish-subscribe" (pub-sub) bus, +where messages are sent on certain 'topics' (subjects) and modules subscribe +only for the topics that they are interested in. + +```mermaid +flowchart LR + A(Module A) + B(Module B) + C(Module C) + MB(Message Bus) + + A -- Publish --> MB + MB -- Subscribe --> B + MB -- Subscribe --> C +``` + +## The benefits + +This philosophy has a number of benefits: + +1. Modules can be developed and tested in isolation +2. Modules can be replaced, added or removed without affecting others +3. All the communication between modules can be traced and diagnosed +4. The system can easily be extended, specialised or upgraded without + any involvement of the original development team +5. The system naturally runs in parallel - each module can run + independently, and can be horizontally scaled - you can have + multiple modules doing the same work. + +## The costs + +Of course, this flexibility comes at some potential cost: + +1. The cost of creating, sending, receiving and reading the messages, + instead of a simple function call +2. The code complexity of publishing and subscribing to a message bus +3. Some actions - API queries, for example - genuinely are + request-response based, and handling that on a pub-sub bus can be + complex + +Mitigating these costs is why we have the [Caryatid](https://github.com/input-output-hk/caryatid) +framework that Acropolis is built on. + +The cost of message passing only happens if you need to serialise and deserialise messages +to pass them between processes over an external bus. Although this mode is possible in Caryatid - +to add third-party extensions, for example - its default mode is combining all the modules into +a single process and using in-memory message passing of native data (Rust structs), avoiding all +that overhead. + +Caryatid also provides simple subscription and publication functions, abstracting away the +complexity of the message bus, and making it almost as simple as a function definition or call. + +Finally, Caryatid provides a request-response layer over the raw message bus, which makes it as +easy to send or handle a request as it would be in any RPC system. + +```mermaid +flowchart TB +subgraph P1[Process 1] + direction TB + subgraph AC[Acropolis] + A(Module A) + B(Module B) + end + subgraph CA[Caryatid] + direction TB + RR[Request-Response] + ROUTE[Message Routing] + IMB[In-memory bus] + XMB[External bus] + + RR <--> ROUTE + ROUTE <--> IMB + ROUTE <--> XMB + end + + A <--> RR + B <--> RR +end + +subgraph P2[Optional Process 2] + direction TB + subgraph AC2[Acropolis] + C(Module C) + end + subgraph CA2[Caryatid] + direction TB + RR2[Request-Response] + ROUTE2[Message Routing] + XMB2[External bus] + IMB2[In-memory bus] + + RR2 <--> ROUTE2 + ROUTE2 <--> XMB2 + ROUTE2 <--> IMB2 + end + + C <--> RR2 +end + +RMQ[Rabbit MQ] +XMB <--> RMQ +XMB2 <--> RMQ + +classDef optional fill:#fffff8,stroke:#ccc +class AC2 optional +class P2 optional +class CA2 optional + +classDef optmod fill:#f8f8ff,stroke:#ccc +class RMQ optmod +class C optmod +class RR2 optmod +class ROUTE2 optmod +class IMB2 optmod +class XMB2 optmod +class XMB optmod +``` + +## Simplification for design + +When we talk about the design of Acropolis and how all the modules interact, we gloss over +the publish-subscribe message flow and message bus, and draw and describe the system as if +the modules talked directly to each other. For many message flows in a realistic system they +are actually one publisher and one subscriber anyway. In some cases - the transaction certificates +output by TxUnpacker is a good example - it is one publisher and many subscribers. There can be +multiple publishers on the same topic, too - for example a number of different sources publish +blocks to be proceesed. + +A simple graph diagram covers all these cases: + +```mermaid +flowchart LR + A(Module A) + B(Module B) + C(Module C) + D(Module D) + + A -- Message 1 --> B + B -- Message 2 --> D + C -- Message 2 --> D + A -- Message 1 --> C + B -- Message 3 --> C +``` + +and this is how we describe the system in these pages. diff --git a/docs/architecture/system-bootstrap-and-sync-with-basic-ledger.md b/docs/architecture/system-bootstrap-and-sync-with-basic-ledger.md new file mode 100644 index 00000000..f268dcd5 --- /dev/null +++ b/docs/architecture/system-bootstrap-and-sync-with-basic-ledger.md @@ -0,0 +1,226 @@ +# System description - bootstrap and sync with basic ledger + +Previously we created a [simple UTXO follower with live sync](system-simple-mithril-and-sync-utxo.md) +which only tracked UTXOs. Now we want to add a more complete ledger, with tracking of +Stake Pool Operators (SPOs), delegation, rewards, reserves and treasury. + +For this we need to add some more modules: + +* [SPO State](../../modules/spo_state) which tracks SPO registration and retirement +* [Epochs State](../../modules/epochs_state) which counts blocks and fees for each epoch +* [Accounts State](../../modules/accounts_state) which tracks stake address balances, SPO delegation, monetary and reward accounts +* [Stake Delta Filter](../../modules/stake_delta_filter) which handles stake pointer addresses + +## Module graph + +```mermaid +flowchart LR + GEN(Genesis Bootstrapper) + MSF(Mithril Snapshot Fetcher) + PNI(Peer Network Interface) + BU(Block Unpacker) + TXU(Tx Unpacker) + UTXO(UTXO State) + SPO(SPO State) + ES(Epochs State) + AC(Accounts State) + SDF(Stake Delta Filter) + PARAM(Parameters State) + GOV(Governance State) + + GEN -- cardano.sequence.bootstrapped --> MSF + MSF -- cardano.block.available --> BU + MSF -- cardano.snapshot.complete --> PNI + PNI -- cardano.block.available --> BU + BU -- cardano.txs --> TXU + TXU -- cardano.utxo.deltas --> UTXO + GEN -- cardano.utxo.deltas --> UTXO + UTXO -- cardano.address.delta --> SDF + SDF -- cardano.stake.deltas --> AC + TXU -- cardano.certificates --> SDF + TXU -- cardano.certificates --> SPO + TXU -- cardano.certificates --> AC + TXU -- cardano.withdrawals --> AC + TXU -- cardano.goverance --> GOV + SPO SPO_AC@-- cardano.spo.state --> AC + GEN -- cardano.pot.deltas --> AC + TXU -- cardano.block.txs --> ES + ES ES_AC@-- cardano.epoch.activity --> AC + PARAM PARAM_GOV@-- cardano.protocol.parameters --> GOV + PARAM PARAM_AC@-- cardano.protocol.parameters --> AC + GOV GOV_PARAM@ -- cardano.enact.state --> PARAM + + click GEN "https://github.com/input-output-hk/acropolis/tree/main/modules/genesis_bootstrapper/" + click MSF "https://github.com/input-output-hk/acropolis/tree/main/modules/mithril_snapshot_fetcher/" + click PNI "https://github.com/input-output-hk/acropolis/tree/main/modules/peer_network_interface/" + click BU "https://github.com/input-output-hk/acropolis/tree/main/modules/block_unpacker/" + click TXU "https://github.com/input-output-hk/acropolis/tree/main/modules/tx_unpacker/" + click UTXO "https://github.com/input-output-hk/acropolis/tree/main/modules/utxo_state/" + click SPO "https://github.com/input-output-hk/acropolis/tree/main/modules/spo_state/" + click ES "https://github.com/input-output-hk/acropolis/tree/main/modules/epochs_state/" + click AC "https://github.com/input-output-hk/acropolis/tree/main/modules/accounts_state/" + click SDF "https://github.com/input-output-hk/acropolis/tree/main/modules/stake_delta_filter/" + click PARAM "https://github.com/input-output-hk/acropolis/tree/main/modules/parameters_state/" + click GOV "https://github.com/input-output-hk/acropolis/tree/main/modules/governance_state/" + + classDef NEW fill:#efe + class SPO NEW + class ES NEW + class AC NEW + class SDF NEW + class PARAM NEW + class GOV NEW + + classDef EPOCH stroke:#008 + class SPO_AC EPOCH + class ES_AC EPOCH + class PARAM_GOV EPOCH + class PARAM_AC EPOCH + class GOV_PARAM EPOCH +``` + +## Data flow + +The process bootstraps from Mithril, then syncs from the live chain and tracks UTXOs exactly +as [before](system-simple-mithril-and-sync-utxo.md). We will add much more comprehensive +tracking of the ledger state for the Shelley era only for now - Conway governance will +come later. + +### SPOs +The first thing we need to track are Stake Pool Operators. This is done with a new +[SPO State](../../modules/spo_state) module. It subscribes to `cardano.tx.certificates` +produced by the [TX Unpacker](../../modules/tx_unpacker), which carry most of the 'events' +to do with chain management. In this case it is just interested in SPO registrations +and deregistrations (retirements). It keeps an internal store of these and issues a complete +list of all active SPOs and their details at the start of each epoch, on `cardano.spo.state`. + +Note that this message is the first we've seen that happens on each *epoch* rather than +each *block*. We colour these in blue in the diagram above. + +### Accounts State +This message is picked up by the new [Accounts State](../../modules/accounts_state) module. +Accounts State has a lot a functions - we'll discuss why they are all combined later - but +its primary output is the Stake Pool Delegation Distribution (SPDD) which gives the total +stake (both UTXOs and rewards) delegated to each SPO. This is a core part of the Ouroboros +protocol, since it defines which SPOs are allowed to produce blocks. + +In order to do this, Accounts State also tracks the value of each stake address. Remember that +Cardano addresses can (and usually do) have two parts, the payment address (`addr1xxx`) and the +stake address (`stake1xxx`). It is the stake address that people usually think of as the 'wallet', +and can have multiple payment addresses associated with it. It is also - as its name implies - +the thing that is delegated to SPOs. + +When a UTXO is created (a transaction output) or spent (by a transaction input), the +[UTXO State](../../modules/utxo_state) we've already seen sends a `cardano.address.delta` +message with the full address (both payment and stake part) and the change of value. This +should be enough for the Accounts State to track the value, but there's a complication... + +### Stake address pointers +There is another form of stake address which is a pointer (by slot, +transaction index and certificate index) to the stake registration +certificate. This was supposed to save space compared to the full +address format, but it was hardly ever used (only 5 exist on mainnet!) +and has now been withdrawn, although the old ones are still valid. + +To handle this, we add another module, the [Stake Delta Filter](../../modules/stake_delta_filter) +which keeps a list of all the stake delegations, which it receives from `cardano.certificates` +and converts any pointers into their full form. +It also filters out any address deltas that don't include any stake address information (some +addresses don't). The cleaned-up deltas are then published on `cardano.stake.deltas`, which +is what the Accounts State actually subscribes to. + +### Epoch activity + +Another new module, [Epoch State](../../modules/epoch_state) counts up all the fees paid on +transactions in each epoch, and also how many blocks each SPO produced. It gets this information +from block data sent on `cardano.block.txs` .It sends the totals to +Accounts State on `cardano.epoch.activity`. + +### Monetary pots +The Accounts State module also tracks the global monetary 'pots', including the reserves, +treasury and deposit accounts. To start this off it receives `cardano.pot.deltas` from the +genesis bootstrapper which sets the initial reserves allocation - at this point the treasury +and deposits are zero. + +Then at the start of each epoch, a proportion of the reserves, plus +the fees, is allocated to the treasury, and a further portion to pay +rewards. + +### Rewards + +The Cardano rewards system is an accounts-based layer on top of the raw UTXO model. Each +stake address has a reward account, and rewards are earned for block production both by SPOs - +to recompense them for running the network - and to ordinary users who delegate their stake +to them - as a kind of yield for holding Ada and participating in the Proof of Stake system. + +The rewards calculation is complex, and deserves its own page (TODO) but at this level we can +survey what is required to do it in the Accounts State module. It needs: + +* The current set of SPOs and all their parameters such as fixed cost and margin +(`cardano.spo.state`) +* Delegation events indicating which stake addresses are delegated to which SPOs +(`cardano.certificates`) +* Stake address deltas (`cardano.stake.deltas`) as already mentioned +* Counts of blocks produced per SPO for each epoch (`cardano.epoch.activity`) + +The result of this is at each new epoch (actually a fixed time into it), +Accounts State looks at each SPO and its success in producing +blocks in the previous epoch, derives a total share of the rewards +available to be paid to that SPO and its delegators, calculates the +amount for the SPO itself, then splits the remainder according to the +stakes of the delegators captured from two epochs ago. These rewards +are then held ready to actually be paid at the start of the next +epoch. + +### Deposits + +Accounts State also needs to track SPO and stake address registrations +and deregistrations to keep account of the deposits, which it receives +through `cardano.tx.certificates`. When an SPO retires, or a stake address +is deregistered, the deposit is paid back to their reward account. + +### Withdrawals + +The value accumulated in a reward account cannot be spent directly like a UTXO, but there is a +mechanism to withdraw it - a transaction can have a withdrawal added, which adds a specified +value to the sum of the input values for the transaction, which can then be moved to other UTXOs. +User wallets usually do this automatically when required so the user isn't aware of it. + +Accounts State gets withdrawal information from `cardano.withdrawals` sent by the Tx Unpacker. + +### Instantaneous Rewards + +In earlier eras of Cardano, there was also the Move Instantaneous +Rewards (MIR) mechanism to move rewards the other way, direct from +reserves or treasury to a reward account. This was used to move +rewards from the Incentivised Testnet (ITN) at the beginning of +Shelley, and occasionally since for adjustments. Since Conway, +new MIRs are no longer allowed. + +Accounts State receives MIRs through the `cardano.certificates` topic, stores +them up and processes them at the start of each epoch. + +### Parameters and Governance + +Although at this stage we aren't handling the full governance of the Conway era (CIP-1694) +even before that there was a governance mechanism to alter protocol parameters, managed +by the founding entities. We need to track this in order to maintain the correct values of +various calculation parameters as we move through the chain. + +To do this, we introduce two new modules: [Governance State](../../modules/governance_state) +and [Parameter State](../../modules/parameter_state). The Governance State gets governance +events from the Tx Unpacker on `cardano.governance`. If anything changes during an epoch, +it sends a `cardano.enact.state` message to the Parameter State, which then sends a +`cardano.protocol.parameters` with the updated parameter set. This is picked up by Accounts +State to get its calculation parameters, and also by Governance State since the parameters +affect the voting system as well. + +## Configuration + +Here is the +[configuration](../../processes/omnibus/configs/bootstrap-and-sync-with-basic-ledger.toml) +for this setup. You can run it in the `processes/omnibus` directory with: + +```shell +$ cargo run --release -- --config configs/bootstrap-and-sync-with-basic-ledger.toml +``` diff --git a/docs/architecture/system-simple-mithril-and-sync-utxo.md b/docs/architecture/system-simple-mithril-and-sync-utxo.md new file mode 100644 index 00000000..980a4623 --- /dev/null +++ b/docs/architecture/system-simple-mithril-and-sync-utxo.md @@ -0,0 +1,76 @@ +# System description - simple UTXO follower with live sync + +Having created a [simple Mithril UTXO follower](system-simple-mithril-utxo.md) we'll now add +the ability to continue after the Mithril snapshot and follow the live chain, fetching data +from a trusted (for now) set of upstream nodes. + +For this we just need to add one module, the +[Peer Network Interface](../../modules/peer_network_interface) +which just plugs into the existing graph: + +## Module graph + +```mermaid +flowchart LR + GEN(Genesis Bootstrapper) + MSF(Mithril Snapshot Fetcher) + PNI(Peer Network Interface) + BU(Block Unpacker) + TXU(Tx Unpacker) + UTXO(UTXO State) + + GEN -- cardano.sequence.bootstrapped --> MSF + MSF -- cardano.block.available --> BU + MSF -- cardano.snapshot.complete --> PNI + PNI -- cardano.block.available --> BU + BU -- cardano.txs --> TXU + TXU -- cardano.utxo.deltas --> UTXO + GEN -- cardano.utxo.deltas --> UTXO + + click GEN "https://github.com/input-output-hk/acropolis/tree/main/modules/genesis_bootstrapper/" + click MSF "https://github.com/input-output-hk/acropolis/tree/main/modules/mithril_snapshot_fetcher/" + click PNI "https://github.com/input-output-hk/acropolis/tree/main/modules/peer_network_interface/" + click BU "https://github.com/input-output-hk/acropolis/tree/main/modules/block_unpacker/" + click TXU "https://github.com/input-output-hk/acropolis/tree/main/modules/tx_unpacker/" + click UTXO "https://github.com/input-output-hk/acropolis/tree/main/modules/utxo_state/" + + classDef NEW fill:#efe + class PNI NEW +``` + +## Data flow + +The process follows exactly the same sequence as [before](system-simple-mithril-utxo.md) initially: + +* [Genesis Bootstrapper](../../modules/genesis_bootstrapper) reads and sends the Genesis UTXOs +* [Mithril Snapshot Fetcher](../../modules/mithril_snapshot_fetcher) reads a block history snapshot and sends out the raw blocks +* [Block Unpacker](../../modules/block_unpacker) decodes the blocks into individual transactions +* [Tx Unpacker](../../modules/tx_unpacker) decodes transactions and produces UTXO deltas +* [UTXO State](../../modules/utxo_state) catches and checks the UTXO deltas and maintains a store of active ones. + +To this we have added a [Peer Network Interface](../../modules/peer_network_interface), which +just slots into the existing graph. + +When the Mithril Snapshot Fetcher has come to the end of the block data +in the snapshot, it sends a `cardano.sequence.bootstrapped` message indicating how far it got (it +was already doing this but no-one was listening, so we didn't mention it before). + +This is then picked up by the Peer Network Interface, which is +configured to be a block fetching client to one or more relays - the +config has some standard ones by default. It continues where the +snapshot left off, fetching blocks from the live network and +publishing them as `cardano.block.available` in the same way. + +Notice that we didn't have to reconfigure anything else - the Block Unpacker +doesn't care where the blocks come from, as long as they are in +sequence, which the hand-off process ensures. This is the benefit of the pub-sub +architecture! + +## Configuration + +Here is the [configuration](../../processes/omnibus/configs/simple-mithril-and-sync-utxo.toml) +for this setup. You can run it in the `processes/omnibus` directory with: + +```shell +$ cargo run --release -- --config configs/simple-mithril-and-sync-utxo.toml +``` diff --git a/docs/architecture/system-simple-mithril-utxo.md b/docs/architecture/system-simple-mithril-utxo.md new file mode 100644 index 00000000..2c457646 --- /dev/null +++ b/docs/architecture/system-simple-mithril-utxo.md @@ -0,0 +1,90 @@ +# System description - starting from basics + +We'll describe how all the modules in Acropolis fit together by building up the full node in +stages, which more-or-less reflects how it was developed. + +To start with, let's create the simplest possible UTXO indexer, booting from genesis (the very +start of the Cardano chain) and using a Mithril block data dump to save fetching from the live +network. + +## Module graph + +```mermaid +flowchart LR + GEN(Genesis Bootstrapper) + MSF(Mithril Snapshot Fetcher) + BU(Block Unpacker) + TXU(Tx Unpacker) + UTXO(UTXO State) + + GEN -- cardano.sequence.bootstrapped --> MSF + MSF -- cardano.block.available --> BU + BU -- cardano.txs --> TXU + TXU -- cardano.utxo.deltas --> UTXO + GEN -- cardano.utxo.deltas --> UTXO + + click GEN "https://github.com/input-output-hk/acropolis/tree/main/modules/genesis_bootstrapper/" + click MSF "https://github.com/input-output-hk/acropolis/tree/main/modules/mithril_snapshot_fetcher/" + click BU "https://github.com/input-output-hk/acropolis/tree/main/modules/block_unpacker/" + click TXU "https://github.com/input-output-hk/acropolis/tree/main/modules/tx_unpacker/" + click UTXO "https://github.com/input-output-hk/acropolis/tree/main/modules/utxo_state/" +``` + +## Data flow + +The process starts with the [Genesis Bootstrapper](../../modules/genesis_bootstrapper) +reading the genesis data file, which was used to initialise the Cardano mainnet chain back +in 2017. At this stage, all we're interested in are the "Genesis +UTXOs" - these are the initial distribution of Ada based on the +purchases from the pre-launch "vending machine" (AVVM) process. It +sends these as a `cardano.utxo.deltas` message to the UTXOState +(remembering we are describing this with a gods' eye view of who +subscribes to that topic - the Genesis Bootstrapper doesn't know or care who is listening). +It then sends a `cardano.sequence.bootstrapped` which triggers the next stage. + +This is picked up by the [Mithril Snapshot Fetcher](../../modules/mithril_snapshot_fetcher), +which downloads a 'snapshot' containing the entire block history of the chain (up to a few hours +ago), if it doesn't already have a sufficiently recent one. You can configure what 'sufficiently +recent' is in its section in the config file. When it has downloaded the snapshot (about 50GB at +the time of writing), verified and unpacked it, it starts sending `cardano.block.available` +messages with the raw data for every block in the history. + +The [Block Unpacker](../../modules/block_unpacker) receives the +`cardano.block.available` messages and, as its name suggests, decodes +the raw data and unpacks it into individual transactions. It then +constructs and sends a `cardano.txs` message containing the raw data +for all the transactions in the block. Note it sends a *single* +message for each block, containing a batch of transactions - this is +the standard pattern in Acropolis, and both improves performance by +reducing the number of messages processed and is also key to +[synchronisation](synchronisation.md) across topics. + +The `cardano.txs` message is picked up by the [Tx Unpacker](../../modules/tx_unpacker) which +decodes each transaction and outputs messages for all the many things that can be contained +in one. In this simple system we will only ask it to extract UTXO changes, or *deltas* - either +inputs, which spend an existing UTXO, or outputs, which create a new one. It will send these +on `cardano.utxo.deltas`. + +The final stage of our simple pipeline is the [UTXO State](../../modules/utxo_state), which +receives the UTXO deltas and applies them to its internal store of UTXOs, removing those that +are spent by inputs, and adding those that are created by outputs. UTXO State has a number of +options for storage - we'll use the fast, in-memory one by default, which keeps all the UTXO data in +memory, at a cost of around 5GB at the current epoch (575). + +In this simple system, all this does is record the UTXOs, count them, +and flag any errors (which we don't expect to see in mainnet, of +course!). Naturally any real system would have more modules doing useful things with +this data, and/or providing APIs to read it, as we'll see as we expand our setup. + +## Configuration + +Here is the [configuration](../../processes/omnibus/configs/simple-mithril-utxo.toml) for this setup. You can run it in the `processes/omnibus` directory with: + +```shell +$ cargo run --release -- --config configs/simple-mithril-utxo.toml +``` + +## Next steps + +Next we'll add a [live network synchronisation](system-simple-mithril-and-sync-utxo.md) after +the bootstrap. diff --git a/modules/accounts_state/src/accounts_state.rs b/modules/accounts_state/src/accounts_state.rs index 11c31b62..c8deedfe 100644 --- a/modules/accounts_state/src/accounts_state.rs +++ b/modules/accounts_state/src/accounts_state.rs @@ -38,13 +38,15 @@ use verifier::Verifier; use crate::spo_distribution_store::{SPDDStore, SPDDStoreConfig}; mod spo_distribution_store; +// Subscriptions const DEFAULT_SPO_STATE_TOPIC: &str = "cardano.spo.state"; const DEFAULT_EPOCH_ACTIVITY_TOPIC: &str = "cardano.epoch.activity"; const DEFAULT_TX_CERTIFICATES_TOPIC: &str = "cardano.certificates"; const DEFAULT_WITHDRAWALS_TOPIC: &str = "cardano.withdrawals"; const DEFAULT_POT_DELTAS_TOPIC: &str = "cardano.pot.deltas"; const DEFAULT_STAKE_DELTAS_TOPIC: &str = "cardano.stake.deltas"; -const DEFAULT_DREP_STATE_TOPIC: &str = "cardano.drep.state"; + +// Publishers const DEFAULT_DREP_DISTRIBUTION_TOPIC: &str = "cardano.drep.distribution"; const DEFAULT_SPO_DISTRIBUTION_TOPIC: &str = "cardano.spo.distribution"; const DEFAULT_SPO_REWARDS_TOPIC: &str = "cardano.spo.rewards"; @@ -79,7 +81,7 @@ impl AccountsState { mut withdrawals_subscription: Box>, mut pots_subscription: Box>, mut stake_subscription: Box>, - mut drep_state_subscription: Box>, + mut drep_state_subscription: Option>>, mut parameters_subscription: Box>, verifier: &Verifier, ) -> Result<()> { @@ -178,28 +180,31 @@ impl AccountsState { } } - // Handle DRep - let (_, message) = drep_state_subscription.read_ignoring_rollbacks().await?; - match message.as_ref() { - Message::Cardano((block_info, CardanoMessage::DRepState(dreps_msg))) => { - let span = info_span!( - "account_state.handle_drep_state", - block = block_info.number - ); - async { - Self::check_sync(¤t_block, block_info); - state.handle_drep_state(dreps_msg); - - let drdd = state.generate_drdd(); - if let Err(e) = drep_publisher.publish_drdd(block_info, drdd).await { - error!("Error publishing drep voting stake distribution: {e:#}") + // Handle DRep (if configured) + if let Some(ref mut subscription) = drep_state_subscription { + let (_, message) = subscription.read_ignoring_rollbacks().await?; + match message.as_ref() { + Message::Cardano((block_info, CardanoMessage::DRepState(dreps_msg))) => { + let span = info_span!( + "account_state.handle_drep_state", + block = block_info.number + ); + async { + Self::check_sync(¤t_block, block_info); + state.handle_drep_state(dreps_msg); + + let drdd = state.generate_drdd(); + if let Err(e) = drep_publisher.publish_drdd(block_info, drdd).await + { + error!("Error publishing drep voting stake distribution: {e:#}") + } } + .instrument(span) + .await; } - .instrument(span) - .await; - } - _ => error!("Unexpected message type: {message:?}"), + _ => error!("Unexpected message type: {message:?}"), + } } // Handle SPOs @@ -406,15 +411,17 @@ impl AccountsState { .unwrap_or(DEFAULT_STAKE_DELTAS_TOPIC.to_string()); info!("Creating stake deltas subscriber on '{stake_deltas_topic}'"); - let drep_state_topic = - config.get_string("drep-state-topic").unwrap_or(DEFAULT_DREP_STATE_TOPIC.to_string()); - info!("Creating DRep state subscriber on '{drep_state_topic}'"); - let parameters_topic = config .get_string("protocol-parameters-topic") .unwrap_or(DEFAULT_PROTOCOL_PARAMETERS_TOPIC.to_string()); info!("Creating protocol parameters subscriber on '{parameters_topic}'"); + // Optional topics + let drep_state_topic = config.get_string("drep-state-topic").ok(); + if let Some(ref topic) = drep_state_topic { + info!("Creating DRep state subscriber on '{topic}'"); + } + // Publishing topics let drep_distribution_topic = config .get_string("publish-drep-distribution-topic") @@ -695,7 +702,10 @@ impl AccountsState { let withdrawals_subscription = context.subscribe(&withdrawals_topic).await?; let pot_deltas_subscription = context.subscribe(&pot_deltas_topic).await?; let stake_subscription = context.subscribe(&stake_deltas_topic).await?; - let drep_state_subscription = context.subscribe(&drep_state_topic).await?; + let drep_state_subscription = match drep_state_topic { + Some(ref topic) => Some(context.subscribe(topic).await?), + None => None, + }; let parameters_subscription = context.subscribe(¶meters_topic).await?; // Start run task diff --git a/modules/governance_state/src/governance_state.rs b/modules/governance_state/src/governance_state.rs index cb57d454..fbac6cbe 100644 --- a/modules/governance_state/src/governance_state.rs +++ b/modules/governance_state/src/governance_state.rs @@ -31,14 +31,12 @@ mod voting_state; use state::State; use voting_state::VotingRegistrationState; -const DEFAULT_SUBSCRIBE_TOPIC: (&str, &str) = ("subscribe-topic", "cardano.governance"); -const DEFAULT_DREP_DISTRIBUTION_TOPIC: (&str, &str) = - ("stake-drep-distribution-topic", "cardano.drep.distribution"); -const DEFAULT_SPO_DISTRIBUTION_TOPIC: (&str, &str) = - ("stake-spo-distribution-topic", "cardano.spo.distribution"); -const DEFAULT_PROTOCOL_PARAMETERS_TOPIC: (&str, &str) = +const CONFIG_GOVERNANCE_TOPIC: (&str, &str) = ("subscribe-topic", "cardano.governance"); +const CONFIG_DREP_DISTRIBUTION_TOPIC: &str = "stake-drep-distribution-topic"; +const CONFIG_SPO_DISTRIBUTION_TOPIC: &str = "stake-spo-distribution-topic"; +const CONFIG_PROTOCOL_PARAMETERS_TOPIC: (&str, &str) = ("protocol-parameters-topic", "cardano.protocol.parameters"); -const DEFAULT_ENACT_STATE_TOPIC: (&str, &str) = ("enact-state-topic", "cardano.enact.state"); +const CONFIG_ENACT_STATE_TOPIC: (&str, &str) = ("enact-state-topic", "cardano.enact.state"); const VERIFICATION_OUTPUT_FILE: &str = "verification-output-file"; @@ -51,9 +49,9 @@ const VERIFICATION_OUTPUT_FILE: &str = "verification-output-file"; pub struct GovernanceState; pub struct GovernanceStateConfig { - subscribe_topic: String, - drep_distribution_topic: String, - spo_distribution_topic: String, + governance_topic: String, + drep_distribution_topic: Option, + spo_distribution_topic: Option, protocol_parameters_topic: String, enact_state_topic: String, governance_query_topic: String, @@ -67,13 +65,21 @@ impl GovernanceStateConfig { actual } + fn conf_option(config: &Arc, key: &str) -> Option { + let actual = config.get_string(key).ok(); + if let Some(ref value) = actual { + info!("Creating subscriber on '{}' for {}", value, key); + } + actual + } + pub fn new(config: &Arc) -> Arc { Arc::new(Self { - subscribe_topic: Self::conf(config, DEFAULT_SUBSCRIBE_TOPIC), - drep_distribution_topic: Self::conf(config, DEFAULT_DREP_DISTRIBUTION_TOPIC), - spo_distribution_topic: Self::conf(config, DEFAULT_SPO_DISTRIBUTION_TOPIC), - protocol_parameters_topic: Self::conf(config, DEFAULT_PROTOCOL_PARAMETERS_TOPIC), - enact_state_topic: Self::conf(config, DEFAULT_ENACT_STATE_TOPIC), + governance_topic: Self::conf(config, CONFIG_GOVERNANCE_TOPIC), + drep_distribution_topic: Self::conf_option(config, CONFIG_DREP_DISTRIBUTION_TOPIC), + spo_distribution_topic: Self::conf_option(config, CONFIG_SPO_DISTRIBUTION_TOPIC), + protocol_parameters_topic: Self::conf(config, CONFIG_PROTOCOL_PARAMETERS_TOPIC), + enact_state_topic: Self::conf(config, CONFIG_ENACT_STATE_TOPIC), governance_query_topic: Self::conf(config, DEFAULT_GOVERNANCE_QUERY_TOPIC), verification_output_file: config .get_string(VERIFICATION_OUTPUT_FILE) @@ -127,8 +133,8 @@ impl GovernanceState { context: Arc>, config: Arc, mut governance_s: Box>, - mut drep_s: Box>, - mut spo_s: Box>, + mut drep_s: Option>>, + mut spo_s: Option>>, mut protocol_s: Box>, ) -> Result<()> { let state = Arc::new(Mutex::new(State::new( @@ -263,26 +269,30 @@ impl GovernanceState { if blk_g.epoch > 0 { // TODO: make sync more stable - let (blk_drep, d_drep) = Self::read_drep(&mut drep_s).await?; - if blk_g != blk_drep { - error!("Governance {blk_g:?} and DRep distribution {blk_drep:?} are out of sync"); - } - - let (blk_spo, d_spo) = Self::read_spo(&mut spo_s).await?; - if blk_g != blk_spo { - error!( - "Governance {blk_g:?} and SPO distribution {blk_spo:?} are out of sync" - ); - } - - if blk_spo.epoch != d_spo.epoch + 1 { - error!( - "SPO distibution {blk_spo:?} != SPO epoch + 1 ({})", - d_spo.epoch - ); + if let Some(ref mut drep_s) = drep_s { + if let Some(ref mut spo_s) = spo_s { + let (blk_drep, d_drep) = Self::read_drep(drep_s).await?; + if blk_g != blk_drep { + error!("Governance {blk_g:?} and DRep distribution {blk_drep:?} are out of sync"); + } + + let (blk_spo, d_spo) = Self::read_spo(spo_s).await?; + if blk_g != blk_spo { + error!( + "Governance {blk_g:?} and SPO distribution {blk_spo:?} are out of sync" + ); + } + + if blk_spo.epoch != d_spo.epoch + 1 { + error!( + "SPO distibution {blk_spo:?} != SPO epoch + 1 ({})", + d_spo.epoch + ); + } + + state.lock().await.handle_drep_stake(&d_drep, &d_spo).await? + } } - - state.lock().await.handle_drep_stake(&d_drep, &d_spo).await? } { @@ -296,9 +306,15 @@ impl GovernanceState { pub async fn init(&self, context: Arc>, config: Arc) -> Result<()> { let cfg = GovernanceStateConfig::new(&config); - let gt = context.clone().subscribe(&cfg.subscribe_topic).await?; - let dt = context.clone().subscribe(&cfg.drep_distribution_topic).await?; - let st = context.clone().subscribe(&cfg.spo_distribution_topic).await?; + let gt = context.clone().subscribe(&cfg.governance_topic).await?; + let dt = match cfg.drep_distribution_topic { + Some(ref topic) => Some(context.clone().subscribe(topic).await?), + None => None, + }; + let st = match cfg.spo_distribution_topic { + Some(ref topic) => Some(context.clone().subscribe(topic).await?), + None => None, + }; let pt = context.clone().subscribe(&cfg.protocol_parameters_topic).await?; tokio::spawn(async move { diff --git a/modules/parameters_state/src/parameters_state.rs b/modules/parameters_state/src/parameters_state.rs index 5ad95ede..7bc8fae0 100644 --- a/modules/parameters_state/src/parameters_state.rs +++ b/modules/parameters_state/src/parameters_state.rs @@ -25,11 +25,11 @@ mod state; use parameters_updater::ParametersUpdater; use state::State; -const DEFAULT_ENACT_STATE_TOPIC: (&str, &str) = ("enact-state-topic", "cardano.enact.state"); -const DEFAULT_PROTOCOL_PARAMETERS_TOPIC: (&str, &str) = +const CONFIG_ENACT_STATE_TOPIC: &str = "enact-state-topic"; +const CONFIG_PROTOCOL_PARAMETERS_TOPIC: (&str, &str) = ("publish-parameters-topic", "cardano.protocol.parameters"); -const DEFAULT_NETWORK_NAME: (&str, &str) = ("network-name", "mainnet"); -const DEFAULT_STORE_HISTORY: (&str, bool) = ("store-history", false); +const CONFIG_NETWORK_NAME: (&str, &str) = ("network-name", "mainnet"); +const CONFIG_STORE_HISTORY: (&str, bool) = ("store-history", false); /// Parameters State module #[module( @@ -42,7 +42,7 @@ pub struct ParametersState; struct ParametersStateConfig { pub context: Arc>, pub network_name: String, - pub enact_state_topic: String, + pub enact_state_topic: Option, pub protocol_parameters_topic: String, pub parameters_query_topic: String, pub store_history: bool, @@ -55,6 +55,14 @@ impl ParametersStateConfig { actual } + fn conf_option(config: &Arc, key: &str) -> Option { + let actual = config.get_string(key).ok(); + if let Some(ref value) = actual { + info!("Parameter value '{}' for {}", value, key); + } + actual + } + fn conf_bool(config: &Arc, keydef: (&str, bool)) -> bool { let actual = config.get_bool(keydef.0).unwrap_or(keydef.1); info!("Parameter value '{}' for {}", actual, keydef.0); @@ -64,11 +72,11 @@ impl ParametersStateConfig { pub fn new(context: Arc>, config: &Arc) -> Arc { Arc::new(Self { context, - network_name: Self::conf(config, DEFAULT_NETWORK_NAME), - enact_state_topic: Self::conf(config, DEFAULT_ENACT_STATE_TOPIC), - protocol_parameters_topic: Self::conf(config, DEFAULT_PROTOCOL_PARAMETERS_TOPIC), + network_name: Self::conf(config, CONFIG_NETWORK_NAME), + enact_state_topic: Self::conf_option(config, CONFIG_ENACT_STATE_TOPIC), + protocol_parameters_topic: Self::conf(config, CONFIG_PROTOCOL_PARAMETERS_TOPIC), parameters_query_topic: Self::conf(config, DEFAULT_PARAMETERS_QUERY_TOPIC), - store_history: Self::conf_bool(config, DEFAULT_STORE_HISTORY), + store_history: Self::conf_bool(config, CONFIG_STORE_HISTORY), }) } } @@ -100,67 +108,74 @@ impl ParametersState { async fn run( config: Arc, history: Arc>>, - mut enact_s: Box>, + mut enact_s: Option>>, ) -> Result<()> { loop { - let (_, message) = enact_s.read().await?; - match message.as_ref() { - Message::Cardano((block, CardanoMessage::GovernanceOutcomes(gov))) => { - let span = info_span!("parameters_state.handle", epoch = block.epoch); - async { - // Get current state and current params - let mut state = { - let mut h = history.lock().await; - h.get_or_init_with(|| State::new(config.network_name.clone())) - }; - - // Handle rollback if needed - if block.status == BlockStatus::RolledBack { - state = history.lock().await.get_rolled_back_state(block.epoch); - } - - if block.new_epoch { - // Get current params - let current_params = state.current_params.get_params(); - - // Process GovOutcomes message on epoch transition - let new_params = state.handle_enact_state(block, gov).await?; + // Normal (Conway) behaviour - fetch from goverance enacted + if let Some(ref mut sub) = enact_s { + let (_, message) = sub.read().await?; + match message.as_ref() { + Message::Cardano((block, CardanoMessage::GovernanceOutcomes(gov))) => { + let span = info_span!("parameters_state.handle", epoch = block.epoch); + async { + // Get current state and current params + let mut state = { + let mut h = history.lock().await; + h.get_or_init_with(|| State::new(config.network_name.clone())) + }; - // Publish protocol params message - Self::publish_update(&config, block, new_params.clone())?; + // Handle rollback if needed + if block.status == BlockStatus::RolledBack { + state = history.lock().await.get_rolled_back_state(block.epoch); + } - // Commit state on params change - if current_params != new_params.params { - info!( - "New parameter set enacted [from epoch, params]: [{},{}]", - block.epoch, - serde_json::to_string(&new_params.params)? - ); - let mut h = history.lock().await; - h.commit(block.epoch, state); + if block.new_epoch { + // Get current params + let current_params = state.current_params.get_params(); + + // Process GovOutcomes message on epoch transition + let new_params = state.handle_enact_state(block, gov).await?; + + // Publish protocol params message + Self::publish_update(&config, block, new_params.clone())?; + + // Commit state on params change + if current_params != new_params.params { + info!( + "New parameter set enacted [from epoch, params]: [{},{}]", + block.epoch, + serde_json::to_string(&new_params.params)? + ); + let mut h = history.lock().await; + h.commit(block.epoch, state); + } } - } - Ok::<(), anyhow::Error>(()) + Ok::<(), anyhow::Error>(()) + } + .instrument(span) + .await?; } - .instrument(span) - .await?; - } - Message::Cardano(( - _, - CardanoMessage::StateTransition(StateTransitionMessage::Rollback(_)), - )) => { - // forward the rollback downstream - config.context.publish(&config.protocol_parameters_topic, message).await?; + Message::Cardano(( + _, + CardanoMessage::StateTransition(StateTransitionMessage::Rollback(_)), + )) => { + // forward the rollback downstream + config.context.publish(&config.protocol_parameters_topic, message).await?; + } + msg => error!("Unexpected message {msg:?} for enact state topic"), } - msg => error!("Unexpected message {msg:?} for enact state topic"), } } } pub async fn init(&self, context: Arc>, config: Arc) -> Result<()> { let cfg = ParametersStateConfig::new(context.clone(), &config); - let enact = cfg.context.subscribe(&cfg.enact_state_topic).await?; + let enact_s = match cfg.enact_state_topic { + Some(ref topic) => Some(cfg.context.subscribe(topic).await?), + None => None, + }; + let store_history = cfg.store_history; // Initalize state history @@ -227,7 +242,7 @@ impl ParametersState { // Start run task tokio::spawn(async move { - Self::run(cfg, history, enact).await.unwrap_or_else(|e| error!("Failed: {e}")); + Self::run(cfg, history, enact_s).await.unwrap_or_else(|e| error!("Failed: {e}")); }); Ok(()) diff --git a/modules/spo_state/src/spo_state.rs b/modules/spo_state/src/spo_state.rs index c9a6912f..6edfe12d 100644 --- a/modules/spo_state/src/spo_state.rs +++ b/modules/spo_state/src/spo_state.rs @@ -92,8 +92,8 @@ impl SPOState { mut block_subscription: Box>, mut withdrawals_subscription: Option>>, mut governance_subscription: Option>>, - mut epoch_activity_subscription: Box>, - mut spdd_subscription: Box>, + mut epoch_activity_subscription: Option>>, + mut spdd_subscription: Option>>, mut stake_deltas_subscription: Option>>, mut spo_rewards_subscription: Option>>, mut stake_reward_deltas_subscription: Option>>, @@ -221,19 +221,21 @@ impl SPOState { // read from epoch-boundary messages only when it's a new epoch if new_epoch { - // Handle SPDD - let (_, spdd_message) = spdd_subscription.read_ignoring_rollbacks().await?; - if let Message::Cardano(( - block_info, - CardanoMessage::SPOStakeDistribution(spdd_message), - )) = spdd_message.as_ref() - { - let span = info_span!("spo_state.handle_spdd", block = block_info.number); - span.in_scope(|| { - Self::check_sync(¤t_block, block_info); - // update epochs_history - epochs_history.handle_spdd(block_info, spdd_message); - }); + if let Some(spdd_subscription) = spdd_subscription.as_mut() { + // Handle SPDD + let (_, spdd_message) = spdd_subscription.read_ignoring_rollbacks().await?; + if let Message::Cardano(( + block_info, + CardanoMessage::SPOStakeDistribution(spdd_message), + )) = spdd_message.as_ref() + { + let span = info_span!("spo_state.handle_spdd", block = block_info.number); + span.in_scope(|| { + Self::check_sync(¤t_block, block_info); + // update epochs_history + epochs_history.handle_spdd(block_info, spdd_message); + }); + } } // Handle SPO rewards @@ -282,28 +284,33 @@ impl SPOState { } // Handle EpochActivityMessage - let (_, ea_message) = epoch_activity_subscription.read_ignoring_rollbacks().await?; - if let Message::Cardano(( - block_info, - CardanoMessage::EpochActivity(epoch_activity_message), - )) = ea_message.as_ref() - { - let span = - info_span!("spo_state.handle_epoch_activity", block = block_info.number); - span.in_scope(|| { - Self::check_sync(¤t_block, block_info); - // update epochs_history - let spos: Vec<(PoolId, usize)> = epoch_activity_message - .spo_blocks - .iter() - .map(|(hash, count)| (*hash, *count)) - .collect(); - epochs_history.handle_epoch_activity( - block_info, - epoch_activity_message, - &spos, + if let Some(epoch_activity_subscription) = epoch_activity_subscription.as_mut() { + let (_, ea_message) = + epoch_activity_subscription.read_ignoring_rollbacks().await?; + if let Message::Cardano(( + block_info, + CardanoMessage::EpochActivity(epoch_activity_message), + )) = ea_message.as_ref() + { + let span = info_span!( + "spo_state.handle_epoch_activity", + block = block_info.number ); - }); + span.in_scope(|| { + Self::check_sync(¤t_block, block_info); + // update epochs_history + let spos: Vec<(PoolId, usize)> = epoch_activity_message + .spo_blocks + .iter() + .map(|(hash, count)| (*hash, *count)) + .collect(); + epochs_history.handle_epoch_activity( + block_info, + epoch_activity_message, + &spos, + ); + }); + } } } @@ -772,37 +779,49 @@ impl SPOState { } // Subscriptions + // Mandatory let certificates_subscription = context.subscribe(&certificates_subscribe_topic).await?; let block_subscription = context.subscribe(&block_subscribe_topic).await?; - let epoch_activity_subscription = - context.subscribe(&epoch_activity_subscribe_topic).await?; - let spdd_subscription = context.subscribe(&spdd_subscribe_topic).await?; let clock_tick_subscription = context.subscribe(&clock_tick_subscribe_topic).await?; + + // Optional depending on store features // only when stake_addresses are enabled let withdrawals_subscription = if store_config.store_stake_addresses { Some(context.subscribe(&withdrawals_subscribe_topic).await?) } else { None }; + // when historical spo's votes are enabled let governance_subscription = if store_config.store_votes { Some(context.subscribe(&governance_subscribe_topic).await?) } else { None }; + // when epochs_history is enabled let spo_rewards_subscription = if store_config.store_epochs_history { Some(context.subscribe(&spo_rewards_subscribe_topic).await?) } else { None }; - // when state_addresses are enabled + let epoch_activity_subscription = if store_config.store_epochs_history { + Some(context.subscribe(&epoch_activity_subscribe_topic).await?) + } else { + None + }; + let spdd_subscription = if store_config.store_epochs_history { + Some(context.subscribe(&spdd_subscribe_topic).await?) + } else { + None + }; + + // when stake_addresses are enabled let stake_deltas_subscription = if store_config.store_stake_addresses { Some(context.subscribe(&stake_deltas_subscribe_topic).await?) } else { None }; - // when state_addresses are enabled let stake_reward_deltas_subscription = if store_config.store_stake_addresses { Some(context.subscribe(&stake_reward_deltas_subscribe_topic).await?) } else { diff --git a/processes/omnibus/configs/bootstrap-and-sync-with-basic-ledger.toml b/processes/omnibus/configs/bootstrap-and-sync-with-basic-ledger.toml new file mode 100644 index 00000000..0033798f --- /dev/null +++ b/processes/omnibus/configs/bootstrap-and-sync-with-basic-ledger.toml @@ -0,0 +1,80 @@ +# Top-level configuration for Acropolis omnibus process +# Bootstrap from Mithril and network sync, with basic ledger +# (SPOs, stake, pots, rewards) + +# ============================================================================ +# Startup Configuration +# ============================================================================ +[global.startup] +method = "mithril" # Options: "mithril" | "snapshot" +topic = "cardano.sequence.start" + +# ============================================================================ +# Bootstrap Module Configurations +# ============================================================================ +[module.genesis-bootstrapper] + +[module.mithril-snapshot-fetcher] +aggregator-url = "https://aggregator.release-mainnet.api.mithril.network/aggregator" +genesis-key = "5b3139312c36362c3134302c3138352c3133382c31312c3233372c3230372c3235302c3134342c32372c322c3138382c33302c31322c38312c3135352c3230342c31302c3137392c37352c32332c3133382c3139362c3231372c352c31342c32302c35372c37392c33392c3137365d" +# Download max age in hours. E.g. 8 means 8 hours (if there isn't any snapshot within this time range download from Mithril) +download-max-age = "never" +# Pause constraint E.g. "epoch:100", "block:1200" +pause = "none" + +# ============================================================================ +# Core Module Configurations +# ============================================================================ +[module.peer-network-interface] +sync-point = "snapshot" +node-addresses = [ + "backbone.cardano.iog.io:3001", + "backbone.mainnet.cardanofoundation.org:3001", + "backbone.mainnet.emurgornd.com:3001", +] +magic-number = 764824073 + +[module.block-unpacker] +# Direct connection without consensus mediation +subscribe-topic = "cardano.block.available" + +[module.tx-unpacker] +publish-utxo-deltas-topic = "cardano.utxo.deltas" +publish-withdrawals-topic = "cardano.withdrawals" +publish-certificates-topic = "cardano.certificates" +publish-governance-topic = "cardano.governance" +publish-block-txs-topic = "cardano.block.txs" +network-name = "mainnet" + +[module.utxo-state] +address-delta-topic = "cardano.address.delta" + +[module.spo-state] +block-subscribe-topic = "cardano.block.available" + +[module.stake-delta-filter] + +[module.epochs-state] +block-subscribe-topic = "cardano.block.available" + +[module.parameters-state] +enact-state-topic = "cardano.enact.state" + +[module.governance-state] + +[module.accounts-state] +verify-pots-file = "../../modules/accounts_state/test-data/pots.mainnet.csv" +verify-rewards-files = "../../modules/accounts_state/test-data/rewards.mainnet.{}.csv" + +[module.clock] + +# ============================================================================ +# Message Bus Configuration +# ============================================================================ +[message-bus.internal] +class = "in-memory" + +# Message routing +[[message-router.route]] # Everything is internal only +pattern = "#" +bus = "internal" diff --git a/processes/omnibus/configs/simple-mithril-and-sync-utxo.toml b/processes/omnibus/configs/simple-mithril-and-sync-utxo.toml new file mode 100644 index 00000000..31101f8f --- /dev/null +++ b/processes/omnibus/configs/simple-mithril-and-sync-utxo.toml @@ -0,0 +1,58 @@ +# Top-level configuration for Acropolis omnibus process +# Basic UTXO follower version from Mithril and then live sync + +# ============================================================================ +# Startup Configuration +# ============================================================================ +[global.startup] +method = "mithril" # Options: "mithril" | "snapshot" +topic = "cardano.sequence.start" + +# ============================================================================ +# Bootstrap Module Configurations +# ============================================================================ +[module.genesis-bootstrapper] + +[module.mithril-snapshot-fetcher] +aggregator-url = "https://aggregator.release-mainnet.api.mithril.network/aggregator" +genesis-key = "5b3139312c36362c3134302c3138352c3133382c31312c3233372c3230372c3235302c3134342c32372c322c3138382c33302c31322c38312c3135352c3230342c31302c3137392c37352c32332c3133382c3139362c3231372c352c31342c32302c35372c37392c33392c3137365d" +# Download max age in hours. E.g. 8 means 8 hours (if there isn't any snapshot within this time range download from Mithril) +download-max-age = "never" +# Pause constraint E.g. "epoch:100", "block:1200" +pause = "none" + +# ============================================================================ +# Core Module Configurations +# ============================================================================ +[module.peer-network-interface] +sync-point = "snapshot" +node-addresses = [ + "backbone.cardano.iog.io:3001", + "backbone.mainnet.cardanofoundation.org:3001", + "backbone.mainnet.emurgornd.com:3001", +] +magic-number = 764824073 + +[module.block-unpacker] +# Direct connection without consensus mediation +subscribe-topic = "cardano.block.available" + +[module.tx-unpacker] +# Publish only UTXO deltas +publish-utxo-deltas-topic = "cardano.utxo.deltas" +network-name = "mainnet" + +[module.utxo-state] + +[module.clock] + +# ============================================================================ +# Message Bus Configuration +# ============================================================================ +[message-bus.internal] +class = "in-memory" + +# Message routing +[[message-router.route]] # Everything is internal only +pattern = "#" +bus = "internal" diff --git a/processes/omnibus/configs/simple-mithril-utxo.toml b/processes/omnibus/configs/simple-mithril-utxo.toml new file mode 100644 index 00000000..22127ca7 --- /dev/null +++ b/processes/omnibus/configs/simple-mithril-utxo.toml @@ -0,0 +1,50 @@ +# Top-level configuration for Acropolis omnibus process +# Basic UTXO follower version from Mithril snapshot only + +# ============================================================================ +# Startup Configuration +# ============================================================================ +[global.startup] +method = "mithril" +topic = "cardano.sequence.start" + +# ============================================================================ +# Bootstrap Module Configurations +# ============================================================================ +[module.genesis-bootstrapper] + +[module.mithril-snapshot-fetcher] +aggregator-url = "https://aggregator.release-mainnet.api.mithril.network/aggregator" +genesis-key = "5b3139312c36362c3134302c3138352c3133382c31312c3233372c3230372c3235302c3134342c32372c322c3138382c33302c31322c38312c3135352c3230342c31302c3137392c37352c32332c3133382c3139362c3231372c352c31342c32302c35372c37392c33392c3137365d" +# Download max age in hours. E.g. 8 means 8 hours (if there isn't any snapshot within this time range download from Mithril) +download-max-age = "never" +# Pause constraint E.g. "epoch:100", "block:1200" +pause = "none" + +# ============================================================================ +# Core Module Configurations +# ============================================================================ + +[module.block-unpacker] +# Direct connection without consensus mediation +subscribe-topic = "cardano.block.available" + +[module.tx-unpacker] +# Publish only UTXO deltas +publish-utxo-deltas-topic = "cardano.utxo.deltas" +network-name = "mainnet" + +[module.utxo-state] + +[module.clock] + +# ============================================================================ +# Message Bus Configuration +# ============================================================================ +[message-bus.internal] +class = "in-memory" + +# Message routing +[[message-router.route]] # Everything is internal only +pattern = "#" +bus = "internal" diff --git a/processes/omnibus/omnibus.toml b/processes/omnibus/omnibus.toml index 9044519d..365cf0cc 100644 --- a/processes/omnibus/omnibus.toml +++ b/processes/omnibus/omnibus.toml @@ -125,8 +125,13 @@ store-votes = false store-drdd = false [module.governance-state] +# Listen for DRep and SPO distributions +stake-drep-distribution-topic = "cardano.drep.distribution" +stake-spo-distribution-topic = "cardano.spo.distribution" [module.parameters-state] +# Listen for governance actions +enact-state-topic = "cardano.enact.state" store-history = false [module.stake-delta-filter] @@ -136,6 +141,9 @@ write-full-cache = "false" [module.epochs-state] [module.accounts-state] +# Optional subscription for when governance is active +drep-state-topic = "cardano.drep.state" + # Enable /epochs/{number}/stakes & /epochs/{number}/stakes/{pool_id} endpoints spdd-retention-epochs = 0 spdd-db-path = "./fjall-spdd" @@ -184,25 +192,21 @@ store-transactions = false address = "127.0.0.1" port = 4340 -[module.spy] # Enable for message spying +#[module.spy] #topic = "cardano.#" # ============================================================================ # Message Bus Configuration # ============================================================================ -[message-bus.external] -class = "rabbit-mq" -url = "amqp://127.0.0.1:5672/%2f" -exchange = "caryatid" +# Enable if external routing required +#[message-bus.external] +#class = "rabbit-mq" +#url = "amqp://127.0.0.1:5672/%2f" +#exchange = "caryatid" [message-bus.internal] class = "in-memory" -workers = 50 -dispatch-queue-size = 1000 -worker-queue-size = 100 -bulk-block-capacity = 50 -bulk-resume-capacity = 75 # Message routing [[message-router.route]] # Everything is internal only