// Copyright (c) The Libra Core Contributors // SPDX-License-Identifier: Apache-2.0 // This file contains proto definitions for performing queries and getting back // results with proofs. This is the interface for a client to query data from // the system. Every query result must include proof so that a client can be // certain that the data returned is valid. A client must verify this proof to // ensure that a node isn't lying to them. // How to verify the response as a client: // (Note that every response comes in the form of GetWithProofResponse which // wraps the inner response elements that correspond to the specific request // types. Below we will assume a single request/response type. The // verification can be extended as needed for multiple types. Also note that we // will use the following notation: resp = GetWithProofResponse and req = // GetWithProofRequest). Also note that the following will be considered // equivalent for brevity: req.requested_items.get_account_state_request == // req.get_account_state_request And, resp.values.get_account_state_response == // resp.get_account_state_response // // GetAccountStateResponse: // - let state_req = req.requested_items.get_account_state_request; // - let state_resp = resp.values.get_account_state_response; // - Verify that: // - state_req.access_path == state_resp.access_path // - This ensures that the server is responding with the correct access // path // - let state_data_hash = Hash(state_resp.value); // - let state_proof = resp.values.proof.state_proof_value.sparse_merkle_proof; // - Validate state_proof using state_data_hash as the leaf // - When verifying the state tree, use: // state_root_hash = resp.values.transaction_info.state_root_hash // - Validate accumulator using resp.values.transaction_info as the leaf // - When verifying the accumulator, use: // root_hash = // resp.ledger_info_with_sigs.ledger_info.ledger_info.txn_root_hash; // - Validate that the transaction root hash submitted in // req.known_value.node_value.txn_root_hash // exists in the proof for accumulator and that the proof is valid with // this hash // - Validate ledger info // - let ledger_info_hash = // Hash(resp.ledger_info_with_sigs.ledger_info.ledger_info); // - Verify signatures from resp.ledger_info_with_sigs.signatures are // signing // ledger_info_hash and that there are >2/3 nodes signing this // correctly // - Validate that the timestamp is relatively recent in // resp.ledger_info_with_sigs.ledger_info.timestamp // // // GetAccountTransactionBySequenceNumberResponse: // - Note that other than type completed_transaction, there will be no proof // returned // since the transaction has not yet been committed. To ensure that a // validator is telling the truth about it not being committed yet, a // client should query for their account state and verify that their // current sequence number is less than what they are searching for with // GetAccountTransactionBySequenceNumberResponse // - let txn = // resp.get_account_transaction_by_sequence_number_response.transaction.committed_transaction; // - let txn_hash = Hash(txn); // - Verify that resp.proof.transaction_info.signed_transaction_hash == txn_hash // - Validate accumulator using resp.proof.transaction_info as the leaf // - When verifying the accumulator, use: // root_hash = // resp.ledger_info_with_sigs.ledger_info.ledger_info.txn_root_hash; // - Validate that the transaction root hash submitted in // req.known_value.node_value.txn_root_hash // exists in the proof for accumulator and that the proof is valid with // this hash // - Validate ledger info // - let ledger_info_hash = // Hash(resp.ledger_info_with_sigs.ledger_info.ledger_info); // - Verify signatures from resp.ledger_info_with_sigs.signatures are // signing // ledger_info_hash and that there are >2/3 nodes signing this // correctly // - Validate that the timestamp is relatively recent in // resp.ledger_info_with_sigs.ledger_info.timestamp // // // GetTransactionsResponse: // - for txn in resp.get_transactions_response.transactions: // - let txn = txn.committed_transaction; // - let txn_hash = Hash(txn); // - Verify that txn.proof.transaction_info.signed_transaction_hash == // txn_hash // - Validate accumulator using txn.proof.transaction_info as the leaf // - When verifying the accumulator, use: // root_hash = // resp.ledger_info_with_sigs.ledger_info.ledger_info.txn_root_hash; // - Verify that transactions are sequential and none are missing // - Validate ledger info // - let ledger_info_hash = // Hash(resp.ledger_info_with_sigs.ledger_info.ledger_info); // - Verify signatures from resp.ledger_info_with_sigs.signatures are // signing // ledger_info_hash and that there are >2/3 nodes signing this // correctly // - Validate that the timestamp is relatively recent in // resp.ledger_info_with_sigs.ledger_info.timestamp // - If the number of transactions returned is less than limit for an ascending // query // or if the requested offset > current version for a descending query, // the client should verify that the timestamp in ledger info is relatively // recent to determine if it is likely that all transactions available were // returned syntax = "proto3"; package types; import "access_path.proto"; import "account_state_blob.proto"; import "events.proto"; import "ledger_info.proto"; import "transaction.proto"; import "validator_change.proto"; // ----------------------------------------------------------------------------- // ---------------- Update to latest ledger request // ----------------------------------------------------------------------------- // This API is used to update the client to the latest ledger version and // optionally also request 1..n other pieces of data. This allows for batch // queries. All queries return proofs that a client should check to validate // the data. // // Note that if a client only wishes to update to the latest LedgerInfo and // receive the proof that this latest ledger extends the client_known_version // ledger the client had, they can simply set the requested_items to an empty // list. message UpdateToLatestLedgerRequest { // This is the version the client already trusts. Usually the client should // set this to the version it obtained the last time it synced with the // chain. If this is the first time ever the client sends a request, it must // use the waypoint hard-coded in its software. uint64 client_known_version = 1; // The items for which we are requesting data in this API call. repeated RequestItem requested_items = 2; } message RequestItem { oneof requested_items { GetAccountStateRequest get_account_state_request = 1; GetAccountTransactionBySequenceNumberRequest get_account_transaction_by_sequence_number_request = 2; GetEventsByEventAccessPathRequest get_events_by_event_access_path_request = 3; GetTransactionsRequest get_transactions_request = 4; } } // ----------------------------------------------------------------------------- // ---------------- Update to latest ledger response // ----------------------------------------------------------------------------- // Response from getting latest ledger message UpdateToLatestLedgerResponse { // Responses to the queries posed by the requests. The proofs generated will // be relative to the version of the latest ledger provided below. repeated ResponseItem response_items = 1; // The latest ledger info this node has. It will come with at least 2f+1 // validator signatures as well as a proof that shows the latest ledger // extends the old ledger the client had. LedgerInfoWithSignatures ledger_info_with_sigs = 2; // Validator change events from what the client last knew. This is used to // inform the client of validator changes from the client's last known version // until the current version repeated ValidatorChangeEventWithProof validator_change_events = 3; } // Individual response items to the queries posed by the requests message ResponseItem { oneof response_items { GetAccountStateResponse get_account_state_response = 3; GetAccountTransactionBySequenceNumberResponse get_account_transaction_by_sequence_number_response = 4; GetEventsByEventAccessPathResponse get_events_by_event_access_path_response = 5; GetTransactionsResponse get_transactions_response = 6; } } // ----------------------------------------------------------------------------- // ---------------- Get account state (balance, sequence number, etc.) // ----------------------------------------------------------------------------- // Gets latest state for an account. message GetAccountStateRequest { // Account for which we are fetching the state. bytes address = 1; } // State information returned by a get account state query. message GetAccountStateResponse { // Blob value representing the account state together with proof the client // can utilize to verify it. AccountStateWithProof account_state_with_proof = 1; } // ----------------------------------------------------------------------------- // ---------------- Get single transaction by account + sequence number // ----------------------------------------------------------------------------- // Get transactions that altered an account - this includes both sent and // received. A user of this should check that the data returned matches what // they expect. As an example, a potential attack vector would be something // like the following: Alice is buying an apple from Bob. Alice's phone signs a // transaction X with sequence number N that pays coins to Bob. Alice transmits // this signature to Bob's payment terminal which then submits the transaction // and checks its status to see if Alice can be given the apple. However, as Bob // is doing this Alice constructs a second transaction X' also with sequence // number N. Alice gets that transaction inserted in the blockchain. If Bob // isn't thoughtful about how he uses this API he may assume that if he asks for // the N'th transaction on Alice's account that when the API returns that this // means the transaction has gone through. The point here is that one should be // careful in reading too much into "transaction X is on the chain" and focus on // the logs, which tell you what the transaction did. // // If a client submitted a transaction, they should also verify that the hash of // the returned transaction matches what they submitted. As an example, if a // client has two wallets that share the same account, they may both submit a // transaction at the same sequence number and only one will be committed. A // client should never assume that if they receive the response that this // transaction was included that it means that this is definitely the // transaction that was submitted. They should check that the hash matches what // they sent message GetAccountTransactionBySequenceNumberRequest { // Account for which to query transactions bytes account = 1; uint64 sequence_number = 2; // Set to true to fetch events for the transaction at this version bool fetch_events = 3; } // Transaction information for transactions requested by // GetAccountTransactionsRequest message GetAccountTransactionBySequenceNumberResponse { // When the transaction requested is committed, return the committed // transaction with proof. SignedTransactionWithProof signed_transaction_with_proof = 2; // When the transaction requested is not committed, we give a proof that // shows the current sequence number is smaller than what would have been if // the transaction was committed. AccountStateWithProof proof_of_current_sequence_number = 3; } // ----------------------------------------------------------------------------- // ---------------- Get events by event access path // ----------------------------------------------------------------------------- // Get events that exist on an event access path. In the current world, // a user may specify events that were received, events that were sent, or any // event that modifies their account message GetEventsByEventAccessPathRequest { AccessPath access_path = 1; // The sequence number of the event to start with for this query. Use a // sequence number of MAX_INT to represent the latest. uint64 start_event_seq_num = 2; // If ascending is true this query will return up to `limit` events that were // emitted after `start_event_seq_num`. Otherwise it will return up to `limit` // events before the offset. Both cases are inclusive. bool ascending = 3; // Limit number of results uint64 limit = 4; } message GetEventsByEventAccessPathResponse { // Returns an event and proof of each of the events in the request. The first // element of proofs will be the closest to `start_event_seq_num`. repeated EventWithProof events_with_proof = 1; // If the number of events returned is less than `limit` for an ascending // query or if start_event_seq_num > the latest seq_num for a descending // query, returns the state of the account containing the given access path // in the latest state. This allows the client to verify that there are in // fact no extra events. // // The LedgerInfoWithSignatures which is on the main // UpdateToLatestLedgerResponse can be used to validate this. AccountStateWithProof proof_of_latest_event = 2; } // ----------------------------------------------------------------------------- // ---------------- Get transactions // ----------------------------------------------------------------------------- // Get up to limit transactions starting from start_version. message GetTransactionsRequest { // The version of the transaction to start with for this query. Use a version // of MAX_INT to represent the latest. uint64 start_version = 1; // Limit number of results uint64 limit = 2; // Set to true to fetch events for the transaction at each version bool fetch_events = 3; } message GetTransactionsResponse { TransactionListWithProof txn_list_with_proof = 1; }