-
Notifications
You must be signed in to change notification settings - Fork 14
Provenance
Provenance API give user access to
-
key->value
history - block header data
- transaction existence in block proof
- block(s) existence in ledger proof
- specific db
key->value
existence at specific (block) time proof
// Provenance access to historical data and data integrity proofs
type Provenance interface {
// GetTxProof returns proof that tx with give id is part of blocks merkle tree (path to root)
GetTxProof(txId []byte) (*types.TxProof, error)
// GetBlocksProof returns proof that each block in list of blocks connected to another and to genesis
// by providing shortest path in skip list between blocks in list
// Please note that genesis block index (0) is not part of arguments - no need to pass same argument over nd over again
GetBlocksProof(blocks []uint64, ledgerheight uint64) (*types.BlockProof, error)
// GetHistory return full history for given key in db
GetHistory(dbName, key string) (HistoryIterator, error)
// GetTransaction returns transaction envelope by its id
GetTransaction(txId []byte) (*types.TransactionEnvelope, error)
// GetTransactionBlockHeader returns block header that contains transaction by txid
GetTransactionBlockHeader(txId []byte) (*types.BlockHeader, error)
// GetBlockHeader returns block header
GetBlockHeader(number uint64) (*types.BlockHeader, error)
// GetLstBlockHeader returns block header of last known block in ledger
GetLastBlockHeader() (*types.BlockHeader, error)
// GetStateProof returns merkle-patricia trie path to given state in given block
GetStateProof(ledgerheight uint64, db, key string) ([][]byte, error)
}
type HistoryIterator interface {
Next() (*api.HistoricalData, error)
Close()
}
Based on this API, we can check
- ledger integrity by accessing block headers and validating consistency of ledger merkle list
- transaction existence proof composed of merkle tree path to transaction in block and block existence proof:
GetTxProof()
andGetBlocksProof()
- block existence proof algorithm described in Transaction-Proofs-Skiplist, proof data accessed using GetBlockProof() API
- proof of active and past states can be done by
GetStateProof()
proving existence of all state changes- retrieve
key->value
history by usingGetHistory()
API and provide proofs for all transaction references returned- referenced blocks existence proof in this case can be done by single call to GetBlockProof() with (genesis, history referenced blocks, last block) argument
- retrieve
Block header contains all data required to prove existence of transaction or/and specific state at block time
- Block number
- List of hashes (pointers) in ledger skip list
- Block time
- Two merkle tree roots
- Transactions merkle tree root
- DB state merkle-patricia tree root - ethereum style
// BlockHeaderBase holds the block metadata and the chain information
// that computed before transaction validation
message BlockHeaderBase {
uint64 number = 1;
// Hash of (number - 1) BlockHeaderBase
bytes previous_base_header_hash = 2;
// Root of Merkle tree that contains all transactions, without validation data
bytes tx_merkel_tree_root_hash = 3;
// Hash of BlockHeader of last block already committed to ledger
bytes last_committed_block_hash = 4;
// Number of last block already committed to ledger
uint64 last_committed_block_num = 5;
}
// BlockHeader holds, in addition to base header, extra block metadata and the chain information
// that computed after transactions validation
message BlockHeader {
BlockHeaderBase base_header = 1;
// Skip chain hashed, based of BlockHeader hashed of blocks connected in blocks skip list
repeated bytes skipchain_hashes = 2;
// Root hash of system wide state merkle-particia tree
bytes state_merkel_tree_root_hash = 3;
// Validation info for transactions in block.
repeated ValidationInfo validation_info = 4;
}
Block X proof contains connected list of blocks from the latest ledger block to block X and from the block X to genesis
message BlockProof {
uint64 block_number = 1;
repeated BlockHeader path = 2;
}
Transaction contains block header and path in merkle tree in the block to tree root
message TxProof {
BlockHeader header = 1;
repeated bytes path = 2;
}
State proof block header and path in merkle-patricia tree in the block to tree root
message StateProof {
BlockHeader header = 1;
repeated Node path = 2;
}
Detailed explanation for transaction existence proof, based on data stored during tx commit
- During transaction commit, block header returned as proof
- At some point in future we need to prove that tx is part of ledger, using proof stored since tx commit
- First, we receive last block header by calling
GetLastBlock()
- Genesis block was stored by user apriori
- We call
GetBlockProof(genesis, blockX, lastBlock)
and validate proof by validating blocks pointers - Second, we call
GetTxProof()
for tx path in the block merkle tree andGetTransaction()
for tx content - We validate proof using tx hash and merkle root stored in block header
- First, we receive last block header by calling
Detailed explanation of existence of specific value at specific time explained in certificate management use case. Value change in block X proof explained in asset transfer use case.
To check whole history of specific key in database, we use GetHistory
API who provides all values of specific keys over time, including references to transactions that change this key.
It is possible form this data, using GetTxProof
, GetBlockProof
and GetTransactions
API, to provide proof of change of specific key at specific time in specific transaction.
Instead using GetTxProof
and GetTransaction
, we can use GetStateProof
, just to prove state changes in blocks.
Worth to note that GetTxProof
and GetTransaction
provides addition transaction authenticity proof based on transaction signature, but not always can be used due ALC restrictions.
As we can see here, the API is Blockchain DB installation level API, not single DB level API and it exposed by DBConnector
, but this need to be discussed.
// DBConnector handle connectivity between sdk and Blockchain Database cluster
type DBConnector interface {
...
// GetProvenance returns blockchain db provenance interface
GetProvenance() Provenance
}
As mentioned in Transaction document, during provenance data creation phase,
{Key, newValue, BlockTime, BlockNumber, TxTime, TxId, ClientId, IsDelete, IsOk}
tuple stored as provenance data for each key in WSet.
GetHistory
returns slice of history values based on data stored in provenance tuple
message HistoricalValue {
// DB key
string key = 1;
// Historical value
bytes value = 2;
// Sequencer time when block was created
google.protobuf.Timestamp blocktime = 3;
// Transaction that did the change
bytes txId = 4;
// Block number that holds changing tx
uint64 blocknumber = 5;
// Is this tx deleted this key
bool isDelete = 6;
}
As mentioned earlier, in addition to block existence proofs and transaction existence proofs, we need to prove db state at the specific time (block). For that we will build ethereum inspired
Patricia-Merkle trie for all key->value
pairs that exist - db state tree.
This tree is an optimized dictionary (radix) tree, that use hexadecimal key
representation as path in tree.
Worth to mention that this tree should provide time travelling and only part of tree related to keys in tx wset actually stored with block, and the data in the old block can still be accessed normally.
User wants to know whole asset history, specially asset transfers.
Asset stored as data entity assetId->asset
in database, one of its fields is asset owner
To prove specific asset transfer took place between A and B, user should prove that in block X asset owner field had value A and in block X + 1 it had value B.
Proof itself composed of blocks X and X + 1 headers.
- To get the proof
- User get whole asset history using
GetHistory()
- Block with required asset transfer tx located - transaction that changes owner field to B
- User gets Block header for this block and previous one using
GetBlockHeader()
- User get whole asset history using
- To validate the proof
- The proof of blocks X and X+1 existence generated bt single call to
GetBlockProof()
- The proof of new state (owner=B) for the specific asset generated using
GetStateProof()
- Proof of previous state generated by using
GetStateProof()
with the previous block and owner (owner=A) - State proofs validated using path validation, block proof validation described in Transaction-Proofs-Skiplist
- The proof of blocks X and X+1 existence generated bt single call to
User wants to provide proof he had specific certificate at specific time.
To prove existence of specific certificate at specific time, user has to locate ledger block (X) with the biggest time of all blocks with time smaller of specific time. This will provide proof of certificate existence at block time.
To validate the proof:
- Proof of block X existence generated using
GetBlockProof()
- Proof of certificate issuing generated using
GetStateProof()
- State proofs validated using path validation, block proof validation described in Transaction-Proofs-Skiplist
If we generate proofs for latest block, for example, we can validate certificate validity until now
For now, we go with the simplest approach - if user now have read access to given key, it can access its historical data as well, even if it didn't has access to this key in the past.
To eliminate difference between client clocks, Transaction time set to be equal to block time. We need to add time to block header.
- Servers contain correct, non altered ledger.
- No malicious servers, i.e. each server returns "honest" response for any claim
- Is block part of ledger? Block header is a claim.
- Is tx part of block? Block header and full tx content is a claim.
- Is particular state exist at a block X?
- There is possibility of MiTM attack, although I can't think about any scenario that server signed time anchored response can be pre-collected and used for attack
- There are two different approach how server validate claims:
- Server response can replay with full proof, QLDB/Bitcoin/whatever ledger you want style and user validate this proof using its knowledge about system internals - Merkle trees, Radix trees, Skip lists, etc
- Response contains claim itself, time anchor in way of the latest ledger block and just simple "yes/no" answer
This is smaller REST API that should be exposed from server to SDK in order to implement API discussed above.
We support getting history with pagination, based on last returned an item index and page size
GET /history/{key}?index={skipIndex}&pageSize={size}
It is more or less same API as exposed to used
GET /ledger/tx/{txId}
GET /ledger/block/{blockId}
GET /ledger/height
GET /ledger/blockByTx/{txId}
I am not sure how to implement blocks proof request, that may contain
GET /proof/block/ (????)
GET /proof/tx/{txId}
GET /proof/state/{height}/{db}/{key}
- Because of ALC restrictions, specific user can be allowed to see only some parts of transaction RSet and WSet
- Is it possible to arrange tx rset and wset entries in merkle tree like structure, to prove existing of specific RW entity without reviling all tx?
- Do we need raw tx access/proof API at all?
- As proof, do we want a server to return only signed "yes/no" answer of whole proof(?) and leave to user to validate it?
- How to implement GetBlocksProof() for multiple blocks with REST API?