0xPolygonMiden / miden-client

Client library that facilitates interaction with the Miden rollup
MIT License
32 stars 28 forks source link

Client structure and API #44

Closed bobbinth closed 8 months ago

bobbinth commented 10 months ago

A few thoughts about how the Client struct could look like. These are just proposals at this point and we don't need to implement everything here right away. If we agree on this general approach, we can move the Client struct close to this over time.

Overall, the main goal is to move more logic into the Client struct (or related structs) and make the CLI part relatively "dumb". The benefit of this is that this Client struct could be used by other "front-ends" without requiring this front end to implement lots of redundant logic.

Another goal is to make the client "modular" - that is, have several internal components in the client. For now, these components can be just structs, but over time, we can make them into generic types.

For example, the client struct could look something like this (just a sketch, probably missing something):

pub struct Client {
    /// Local database containing information about the accounts managed by this client.
    store: Store,
    /// Connection to Miden Node.
    node: Node,
    /// Component responsible for executing transactions.
    tx_executor: TransactionExecutor,
    /// Component responsible for proving transactions.
    tx_prover: TransactionProver,
    /// Component responsible for authenticating transactions (i.e., generating signatures).
    authenticator: Authenticator,
    /// Component responsible for generating new keys, serial numbers, etc.
    rng: Rng,
}

And in the future, this could become something like:

pub struct Client<S: Store, N: Node, A: Authenticator, R: Rng> {
    store: S,
    node: N,
    tx_executor: TransactionExecutor,
    tx_prover: TransactionProver,
    authenticator: A,
    rng: R,
}

In terms of API, the client could look something like this:

impl Client {
    // ACCOUNT CREATION
    // --------------------------------------------------------------------------------------------

    /// Replaces current `insert_account` but contains the logic for processing account templates.
    /// 
    /// [AccountTemplate] could include a variant which passes an already constructed account to
    /// allow users to build accounts externally. Alternatively, we could have a separate method
    /// for this - something like `new_account_raw()`.
    pub fn new_account(&mut self, template: AccountTemplate) -> Result<AccountId, ClientError> {
        todo!()
    }

    // DATA RETRIEVAL
    // --------------------------------------------------------------------------------------------

    // client metadata retrieval methods
    // account retrieval methods
    // note retrieval methods
    // transaction retrieval methods

    // STATE SYNCHRONIZATION
    // --------------------------------------------------------------------------------------------

    /// Retrieves the latest state from the chain and returns the most recent block number (i.e.,
    /// the block number to which we are currently synchronized).
    pub fn sync_state(&mut self) -> Result<u32, ClientError> {
        todo!()
    }

    /// Registers a note communicated via a side channel with this client.
    /// 
    /// This is needed so that when the client gets a note hash from the chain, it can figure out
    /// the details of the actual note.
    pub fn register_note(&mut self, note: Note) -> Result<(), Client> {
        todo!()
    }

    // TRANSACTION
    // --------------------------------------------------------------------------------------------

    /// Creates and executes a transactions specified by the template, but does not change the 
    /// local database. The template variants could be something like:
    /// - PayToId - to create a P2ID note directed to a specific account.
    /// - PayToIdWithRecall - same as above but with P2IDR.
    /// - ConsumeNotes - to consume all outstanding notes for some account.
    /// - Custom/Raw - to specify transaction details manually.
    /// 
    /// The returned [Transaction] object would probably be similar (if not identical) to our current
    /// [TransactionResult].
    pub fn new_transaction(&self, template: TransactionTemplate) -> Result<Transaction, ClientError> {
        todo!()
    }

    /// Proves the specified transaction, submits it to the node, and saves the transaction into
    /// the local database.
    pub fn send_transaction(&mut self, transaction: Transaction) -> Result<(), ClientError> {
        todo!()
    }
}

The main idea is that the methods listed above are the only way to modify the client state (i.e., we don't update account states or insert notes directly).

And again, this is just a general sketch - so, any feedback is welcome.

igamigo commented 10 months ago

Regarding the account creation logic, we refactored it: https://github.com/0xPolygonMiden/miden-client/pull/49 In regards to the transaction API, in general I agree but I wonder if we want to make it a bit more granular (ie, execute the transaction, prove it and submit/save it through 3 different calls that the user has to handle). I updated the feature PR to reflect this API change: https://github.com/0xPolygonMiden/miden-client/pull/38

bobbinth commented 10 months ago

In regards to the transaction API, in general I agree but I wonder if we want to make it a bit more granular (ie, execute the transaction, prove it and submit/save it through 3 different calls that the user has to handle).

I'm not opposed to this. Not sure I see a lot of value in breaking out proving and saving/sending - but if you thing it is better, let's go for it.

bobbinth commented 8 months ago

And in the future, this could become something like:

pub struct Client<S: Store, N: Node, A: Authenticator, R: Rng> {
    store: S,
    node: N,
    tx_executor: TransactionExecutor,
    tx_prover: TransactionProver,
    authenticator: A,
    rng: R,
}

Based on some recent conversations, I think we should make the prover field generic too. So, it would look more like so:

pub struct Client<S: Store, P: Prover, N: NodeApi, A: Authenticator, R: Rng> {
    store: S,
    node: N,
    tx_executor: TransactionExecutor,
    tx_prover: P,
    authenticator: A,
    rng: R,
}

pub trait Prover {
    async fn prove<T: Into<TransactionWitness>>(
        &self,
        transaction: T,
    ) -> Result<ProvenTransaction, TransactionProverError>;
}

In the above:

bobbinth commented 8 months ago

Closing this as most of it is already implemented and the remaining pieces can be handled in #128.