From 7824b6cb8bff18aa7064dff35cbfbc655cf0c5ba Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 25 Apr 2023 15:05:58 -0400 Subject: [PATCH] Document the processor/tributary/coordinator/serai flow --- coordinator/src/main.rs | 10 +-- coordinator/src/tributary/scanner.rs | 2 + docs/coordinator/Coordinator.md | 40 +++++++++ docs/coordinator/Tributary.md | 91 +++++++++++++++++++++ docs/processor/Processor.md | 118 +++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 docs/coordinator/Coordinator.md create mode 100644 docs/coordinator/Tributary.md create mode 100644 docs/processor/Processor.md diff --git a/coordinator/src/main.rs b/coordinator/src/main.rs index c80ba71d..01b1905c 100644 --- a/coordinator/src/main.rs +++ b/coordinator/src/main.rs @@ -393,11 +393,11 @@ pub async fn handle_processors( let tributaries = tributaries.read().await; let Some(tributary) = tributaries.get(&genesis) else { - // TODO: This can happen since Substrate tells the Processor to generate commitments - // at the same time it tells the Tributary to be created - // There's no guarantee the Tributary will have been created though - panic!("processor is operating on tributary we don't have"); - }; + // TODO: This can happen since Substrate tells the Processor to generate commitments + // at the same time it tells the Tributary to be created + // There's no guarantee the Tributary will have been created though + panic!("processor is operating on tributary we don't have"); + }; let tributary = tributary.tributary.read().await; if tributary diff --git a/coordinator/src/tributary/scanner.rs b/coordinator/src/tributary/scanner.rs index f5cab36d..4e00124f 100644 --- a/coordinator/src/tributary/scanner.rs +++ b/coordinator/src/tributary/scanner.rs @@ -99,6 +99,8 @@ async fn handle_block( // TODO: We can also full slash if shares before all commitments, or share before the // necessary preprocesses + // TODO: If this is shares, we need to check they are part of the selected signing set + // Store this data let received = TributaryDb::::set_data(label, &mut txn, genesis, id, attempt, signed.signer, &bytes); diff --git a/docs/coordinator/Coordinator.md b/docs/coordinator/Coordinator.md new file mode 100644 index 00000000..a61f0306 --- /dev/null +++ b/docs/coordinator/Coordinator.md @@ -0,0 +1,40 @@ +# Coordinator + +The coordinator is a service which communicates with all of the processors, +all of the other coordinators over a secondary P2P network, and with the Serai +node. + +This document primarily details its flow with regards to the Serai node and +processor. + +## New Set Event + +On `validator_sets::pallet::Event::NewSet`, the coordinator spawns a tributary +for the new set. It additionally sends the processor +`key_gen::CoordinatorMessage::GenerateKey`. + +## Generated Key Pair + +On `key_gen::ProcessorMessage::GeneratedKeyPair`, a +`validator_sets::pallet::vote` transaction is made to vote in the new key. + +The Serai blockchain needs to know the key pair in order for it to be able to +publish `Batch`s. Additionally, having the Serai blockchain confirm the keys +provides a BFT consensus guarantee. While the tributary itself could also offer +a BFT consensus guarantee, there's no point when we'd then get BFT consensus +on the Serai blockchain anyways. + +## Key Generation Event + +On `validator_sets::pallet::Event::KeyGen`, the coordinator sends +`substrate::CoordinatorMessage::ConfirmKeyPair` to the processor. + +# Update + +On `key_gen::ProcessorMessage::Update`, the coordinator publishes an unsigned +transaction containing the signed batch to the Serai blockchain. + +# Sign Completed + +On `sign::ProcessorMessage::Completed`, the coordinator broadcasts the +contained information to all validators. diff --git a/docs/coordinator/Tributary.md b/docs/coordinator/Tributary.md new file mode 100644 index 00000000..e243fa28 --- /dev/null +++ b/docs/coordinator/Tributary.md @@ -0,0 +1,91 @@ +# Tributary + +A tributary is a side-chain, created for a specific multisig instance, used +as a verifiable broadcast layer. + +## Transactions + +### Key Gen Commitments + +`DkgCommitments` is created when a processor sends the coordinator +`key_gen::ProcessorMessage::Commitments`. When all validators participating in +a multisig publish `DkgCommitments`, the coordinator sends the processor +`key_gen::CoordinatorMessage::Commitments`. + +### Key Gen Shares + +`DkgShares` is created when a processor sends the coordinator +`key_gen::ProcessorMessage::Shares`. When all validators participating in +a multisig publish `DkgShares`, the coordinator sends the processor +`key_gen::CoordinatorMessage::Shares`. + +### External Block + +When *TODO*, a `ExternalBlock` transaction is provided. This is used to have +the group acknowledge and synchronize around the block, without the overhead of +voting in its acknowledgment. + +When a `ExternalBlock` transaction is included, participants are allowed to +publish transactions to produce a threshold signature for the block's `Batch`. + +### Substrate Block + +`SubstrateBlock` is provided when the processor sends the coordinator +`substrate::ProcessorMessage::SubstrateBlockAck`. + +When a `SubstrateBlock` transaction is included, participants are allowed to +publish transactions for the signing protocols it causes. + +### Batch Preprocess + +`BatchPreprocess` is created when a processor sends the coordinator +`coordinator::ProcessorMessage::BatchPreprocess` and an `ExternalBlock` +transaction allowing the batch to be signed has already been included on chain. + +When `t` validators have published `BatchPreprocess` transactions, a +`coordinator::ProcessorMessage::BatchPreprocesses` is sent to the processor. + +### Batch Share + +`BatchShare` is created when a processor sends the coordinator +`coordinator::ProcessorMessage::BatchShare`. The relevant `ExternalBlock` +transaction having already been included on chain follows from +`coordinator::ProcessorMessage::BatchShare` being a response to a message which +also has that precondition. + +When the `t` validators who first published `BatchPreprocess` transactions have +published `BatchShare` transactions, a +`coordinator::ProcessorMessage::BatchShares` with the relevant shares is sent +to the processor. + +### Sign Preprocess + +`SignPreprocess` is created when a processor sends the coordinator +`sign::ProcessorMessage::Preprocess` and a `SubstrateBlock` transaction +allowing the transaction to be signed has already been included on chain. + +When `t` validators have published `SignPreprocess` transactions, a +`sign::ProcessorMessage::Preprocesses` is sent to the processor. + +### Sign Share + +`SignShare` is created when a processor sends the coordinator +`sign::ProcessorMessage::Share`. The relevant `SubstrateBlock` transaction +having already been included on chain follows from +`sign::ProcessorMessage::Share` being a response to a message which +also has that precondition. + +When the `t` validators who first published `SignPreprocess` transactions have +published `SignShare` transactions, a `sign::ProcessorMessage::Shares` with the +relevant shares is sent to the processor. + +## Re-attempts + +The key generation and signing protocols, whether batch or transaction, may +fail if a validator goes offline or takes too long to respond. Accordingly, +the tributary will schedule re-attempts. These are communicated with +`key_gen::CoordinatorMessage::GenerateKey`, +`coordinator::CoordinatorMessage::BatchReattempt`, and +`sign::CoordinatorMessage::Reattempt`. + +TODO: Document the re-attempt scheduling logic. diff --git a/docs/processor/Processor.md b/docs/processor/Processor.md new file mode 100644 index 00000000..4854da97 --- /dev/null +++ b/docs/processor/Processor.md @@ -0,0 +1,118 @@ +# Processor + +The processor is a service which has an instance spawned per network. It is +responsible for several tasks, from scanning the connected network to signing +transactions with payments. + +This document primarily discusses its flow with regards to the coordinator. + +## Generate Key + +On `key_gen::CoordinatorMessage::GenerateKey`, the processor begins a pair of +instances of the distributed key generation protocol specified in the FROST +paper. + +The first instance is for a key to use on the connected network. The second +instance is for a Ristretto public key used to publish data to the Serai +blockchain. This pair of FROST DKG instances is considered a single instance of +Serai's overall key generation protocol. + +The commitments for both protocols are sent to the coordinator in a single +`key_gen::ProcessorMessage::Commitments`. + +## Key Gen Commitments + +On `key_gen::CoordinatorMessage::Commitments`, the processor continues the +specified key generation instance. The secret shares for each fellow +participant are sent to the coordinator in a +`key_gen::ProcessorMessage::Shares`. + +### Key Gen Shares + +On `key_gen::CoordinatorMessage::Shares`, the processor completes the specified +key generation instance. The generated key pair is sent to the coordinator in a +`key_gen::ProcessorMessage::GeneratedKeyPair`. + +## Confirm Key Pair + +On `substrate::CoordinatorMessage::ConfirmKeyPair`, the processor starts using +the newly confirmed key, scanning blocks on the connected network for +transfers to it. + +## Connected Network Block + +When the connected network has a new block, which is considered finalized +(either due to being literally finalized or due to having a sufficient amount +of confirmations), it's scanned. + +Outputs to the key of Serai's multisig are saved to the database. Outputs which +newly transfer into Serai are used to build a `Batch` for the block. The +processor then begins a threshold signature protocol with its key pair's +Ristretto key to sign the batch. The protocol's preprocess is sent to the +coordinator in a `coordinator::ProcessorMessage::BatchPreprocess`. + +As a design comment, we *may* be able to sign now possible, already scheduled, +branch/leaf transactions at this point. Doing so would be giving a mutable +borrow over the scheduler to both the external network and the Serai network, +and would accordingly be unsafe. We may want to look at splitting the scheduler +in two, in order to reduce latency (TODO). + +## Batch Preprocesses + +On `coordinator::CoordinatorMessage::BatchPreprocesses`, the processor +continues the specified batch signing protocol, sending +`coordinator::ProcessorMessage::BatchShare` to the coordinator. + +## Batch Shares + +On `coordinator::CoordinatorMessage::BatchShares`, the processor +completes the specified batch signing protocol. If successful, the processor +stops signing for this batch and sends `substrate::ProcessorMessage::Update` to +the coordinator. + +## Batch Re-attempt + +On `coordinator::CoordinatorMessage::BatchReattempt`, the processor will create +a new instance of the batch signing protocol. The new protocol's preprocess is +sent to the coordinator in a `coordinator::ProcessorMessage::BatchPreprocess`. + +## Substrate Block + +On `substrate::CoordinatorMessage::SubstrateBlock`, the processor: + +1) Marks all blocks, up to the external block now considered finalized by + Serai, as having had their batches signed. +2) Adds the new outputs from newly finalized blocks to the scheduler, along + with the necessary payments from `Burn` events on Serai. + +The processor also sends a `substrate::ProcessorMessage::SubstrateBlockAck`, +containing the IDs of all plans now being signed for, to the coordinator. + +## Sign Preprocesses + +On `sign::CoordinatorMessage::Preprocesses`, the processor continues the +specified transaction signing protocol, sending `sign::ProcessorMessage::Share` +to the coordinator. + +## Sign Shares + +On `sign::CoordinatorMessage::Shares`, the processor completes the specified +transaction signing protocol. If successful, the processor stops signing for +this transaction and publishes the signed transaction. Then, +`sign::ProcessorMessage::Completed` is sent to the coordinator, to be +broadcasted to all validators so everyone can observe the transaction was +signed and stop locally attempting to do so. + +## Sign Re-attempt + +On `sign::CoordinatorMessage::Reattempt`, the processor will create a new +a new instance of the transaction signing protocol. The new protocol's +preprocess is sent to the coordinator in a +`sign::ProcessorMessage::Preprocess`. + +## Sign Completed + +On `sign::CoordinatorMessage::Completed`, the processor verifies the included +transaction hash actually refers to an accepted transaction which completes the +plan it was supposed to. If so, the processor stops locally signing for the +transaction, and emits `sign::ProcessorMessage::Completed`.