There were some changes in the API for 1.0 that had removed isRelevant(). What are the best workarounds for this?
Given a use case: If there are 100 parties that want to see this queryable state and all updates pertaining to it (but read-only and doesn't need to sign), do I need to add them all to the Participants list? The "observer" role doesn't exist yet? Is there an alsoInformed or something similar for the use case of just seeing static reference data?
The general function here is a shared linear queryable state where the issuer has the master to change/update which would propagate to all parties that want to "subscribe" to these changes. I believe this might work with a broadcast to a "club", but I don't think clubs exist yet or if they're dynamic groupings of the network map.
Corda is a permissioned or private blockchain as it shares data only with the parties involved in the transaction—unlike public blockchains and DSL-based solutions which broadcast transaction data to the entire network.
No Block, But Chain: Corda's functionality relies on the UTXO input/output model, which is very similar to the transaction system used in traditional blockchains such as Bitcoin.
I'll go into a bit of background before answering... The concept of relevancy still exists in the platform. As you know, in Corda there are two data persistence stores; the storage service and the vault.
The storage service
The storage service is a key -> value store that persists data such as:
The storage service is great for storing large amounts of serialised data that can be indexed and retrieved by hash. However, it is awkward if one wishes to search for data within one of the stored objects. E.g. one cannot easily search for transaction output states of a specific type when using the transaction store. The approach would be to iterate through all transactions, deserialise them one by one, and filter by output type. It's cumbersome and not very efficient. This is why the vault and the concept of relevancy exists!
The vault
The vault exists to store state objects, as opposed to transactions. There is a master states
table where the state reference, transaction id (that generated the output state) and some other meta data such as whether the state is consumed (or not), is stored. There's also a table for LinearState
s and a table for OwnableState
s. Also, if one wishes to add an ORM to their state, a database table is created for each type of state object reflecting the ORM definition. These properties can then be queried to pull out states from the vault that meet specific queries, e.g. "Any obligation states over £1000 with Alice as the lender that have not yet been consumed". That's the power of the vault!
Relevancy
Now, it is the case that not all transactions a node receives produce states that are relevant to that node. An example would be a payment vs payment transaction where Alice sends dollars to Bob and Bob sends pounds to Alice. As Bob now owns the dollars Alice previously owned, those dollars are now not relevant for Alice. As such, Alice shouldn't record the output state representing those dollars as she does not hold the right and obligations to those dollars. What Alice does do is to mark the old dollar state as consumed, thus it will now not count towards her total dollars balance and cannot be used as an input in another transaction (as it has already been spent).
How relevancy works in Corda
So, when a node receives a new transaction, it intersects the public keys defined in the participants
property of each output state with all the public keys that the VaultService
is aware of. If the resultant set fora particular state is not empty, then the state is relevant for the node. Simple.
What this means is that if a node receives a transaction where their public keys are not listed in an output states' participants field, then they will not store that output state in the vault. However, they will store the transaction in the transaction store, and it can still be queried.
The concept of relevancy for OwnableState
s is simple, one either owns it or they don't. The concept for LinearState
s that represent multi-lateral agreements is more complex. In versions M14 and below, one could override the functionality of isRelevant
in a LinearState
, however in V1 this has been removed in favour of an easier approach which just compares the participants keys to the vault keys (as described above).
Implications of the V1 approach to relevancy
As the OP notes, in V1, there will be the concept of transaction observers, where nodes that were not participants of a state can still store the state in their vault and query it as a "third party" state. I.e. it cannot be consumed or contribute to balance totals but it can be queried. In the meantime, we will have to work around the absence of that feature and the options are:
LinearState
s, add all intended observers to the participants
list. Then, add an additional property to the state object called something like consumers
that just contains the list of participants that can consume this state in a valid transaction, i.e. sign a transaction containing it. The contract for that state will then compare those public keys in the commands to those in the consumers
list. This way all the observers will still store the state in their vaults. The FinalityFlow
will broadcast this transaction to all participants
. You can use randomly generated public keys if you don't want the observers to be known to other participants.OwnableState
s, like Cash, there can only be one identity in participants
, the owner. So the approach would be to use the FinalityFlow
to send the transaction to a set of observers, then those observers would have to get the output states directly from the transaction. Cumbersome but temporary as we are working on transaction observers at this moment: https://r3-cev.atlassian.net/browse/CORDA-663.Just a strawman of what I understood if this were to be in code. i.e using obligation cordapp example
Obligation State
val consumers: List<AbstractParty> = listOf(lender, borrower)
override val participants: List<AbstractParty> get() = listOf(lender, borrower, extraActor)
Contract code verify
override fun verify(tx: LedgerTransaction){
val command = tx.commands.requireSingleCommand<Commands>()
when (command.value) {
is Obligation.Issue -> requireThat {
"The signers should only be the consumers for the obligation" using
(command.signers.toSet() == obligation.consumers.map { it.owningKey }.toSet())
}
Add Command specifying the signers need only be consumers during TX creation
val utx = TransactionBuilder(notary = notary)
.addOutputState(state, OBLIGATION_CONTRACT_ID)
.addCommand(Obligation.Issue(), state.consumers.map { it.owningKey })
.setTimeWindow(serviceHub.clock.instant(), 30.seconds)
In this way, the first tx allows the extraActor to commit the state into the ledger without signing. In a future tx proposal, the extraActor
here can query its state table and propose a change of lifecycle in the state using a different command, whereby this time it may require all participants (if need be) to sign the command. i.e Obligation.DoSomethingExtra
command with all participant signing (command.signers.toSet() == obligation.participants.map { it.owningKey }.toSet())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With