question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Corda State Unique Constraints

See original GitHub issue

There is only one mechanism which can be applied to a Corda state in order to ensure uniqueness which is the LinearState. This provides the state with a pseudo-randomly generated, 128 bit, universally unique identifier (UUID) - similar to a primary key in a SQL database.

Problem

Sometimes it makes sense for states to be uniquely constrained on other properties, or composites thereof but unlike a SQL database, there is no mechanism by which Corda states can be uniquely constrained on properties other than the linear ID.

Example

The following example illustrates a simple state that models a friendship between two participants on the network - akin to friendships on Facebook.

data class Friendship(
    override val linearId: UniqueIdentifier,
    val partyA: AbstractParty,
    val partyB: AbstractParty
) : LinearState { ... }

The problem here is that I can issue the same friendship twice, just using two different linear IDs…

val initialFriendship = Friendship(UniqueIdentifier(), alice, bob)
val duplicateFriendship1 = Friendship(UniqueIdentifier(), alice, bob)
val duplicateFriendship2 = Friendship(UniqueIdentifier(), bob, alice)

As far as Corda is concerned they are different enough, however from a business perspective, this wouldn’t make sense as you can’t be friends with the same person more than once. Notice that in this case, I have deliberately added two duplicates, because the ordering of the participants means the same thing.

Corda does allow us to implement QueryableState which allows states to be mapped to a database table, and this feels like a natural place where constraints should apply. With respect to the example above, we can apply the following constraints to our schema:

@Entity
@Table(name = "friendship_states")
@UniqueConstraint(columnNames = ["partyA", "partyB"])
class FriendshipEntity(

    ...

    @Column(name = "partyA", unique = true)
	val partyA: AbstractParty,
	
	@Column(name = "partyB", unique = true)
	val partyB: AbstractParty
) : PersistentState()

This works from the perspective of persisting mapped states (entities), however it still has no impact on the states themselves, the reason being is that persistence of the entity is performed only after the signed transaction has been committed to the vault.

This causes two problems:

  1. The ledger and the entity table are not synchronised.
  2. It causes the node to fail as a result.

Solution

One solution to this would be to check whether any of the states in the transaction implement QueryableState and persist the mapped entities before committing the signed transaction to the vault, thus constituting a single successful (or unsuccessful) transaction, rather than a successful transaction to commit the signed transaction and a potentially unsuccessful transaction to persist the entity.

Workaround

It’s not the most elegant way to enforce this sort of rule at the state level, but the properties at the state level could be hashed together to form a 128 bit hash, which could then by applied to the linear ID…

val initialFriendship(UniqueIdentifier(id = uuid(hash128(sort(alice, bob)))), alice, bob)

val duplicateFriendship1(UniqueIdentifier(id = uuid(hash128(sort(alice, bob)))), alice, bob)
// ^ Should fail because it has a duplicate linear ID

val duplicateFriendship2(UniqueIdentifier(id = uuid(hash128(sort(bob, alice)))), alice, bob)
// ^ Should fail because it has a duplicate linear ID (alice and bob sort to the same order)

Again, not the nicest solution, but I can’t see any other way to force constraints at the moment.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:4
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
adagyscommented, Jun 19, 2020

Ah, got it. So you rely on schema constraints. During FinalityFlow the transaction will get recorded, but when updating the vault tables the uniqueness constraint violation will end up rolling back the db transaction and killing the flow. Thanks

1reaction
woutslakhorstcommented, Sep 17, 2019

Noticed this too, currently we added a PersistentState with a version number which increases every time a state is consumed/produced. Together with a compound unique key, this solves it for now.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Contract Constraints - R3 Documentation
Contract constraints let users know which versions of a CorDapp they can use to provide the contract for a transaction. The constraints property...
Read more >
Contract Upgrades and Constraints in Corda
Constraints are the way that Corda manages contract upgrades. ... Both these flow takes the old states and the new contract class as...
Read more >
Enforcing the uniqueness of property values across Corda ...
How do I ensure that a property of a state is unique for all instances of that state on the ledger? Say I...
Read more >
Upgrading contracts and states in Corda | by Amol Pednekar
Implicit: By allowing multiple implementations of the contract ahead of time, using constraints. · Explicit: By creating a special contract ...
Read more >
R3 Corda - Documentation - Kaleido Docs
Corda has many unique designs compared to most other blockchain ... to Java programmers: Contracts process Transactions that effect States transfers.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found