© Debajani Mohanty 2019
Debajani MohantyR3 Corda for Architects and Developers https://doi.org/10.1007/978-1-4842-4529-3_4

4. Government and Real Estate

Debajani Mohanty1 
(1)
Noida, Uttar Pradesh, India
 

In this chapter, we will learn how Corda can be used as a private permissioned ledger for recording the transactions between different parties in land registry expediting business processes and bringing transparency to the entire ecosystem. This chapter also comes with a sample project that can be downloaded by developers.

Fraud is very high in land- and property-related transactions. This is often due to the fact that even if someone claims to be the rightful owner of a piece of land, he/she might have been duped by another seller who sold the property with fake documents. Also, tracking of the property deal and the ownership history of the property are crucial in property transactions.

Let’s consider a scenario where a property is to be transferred between two different persons. It might involve multiple independent organizations such as a land registry department, a bank, a surveyor, and so on. In the past, such workflows were a paper-based system that consumed a huge amount of time for completion. Nowadays, many organizations have digitized the entire system and yet the basic issue has remained the same. Organizations participating in the workflow do not wish to share their data for a common platform, and hence tracking the transactions at any point of time has always been a challenge.

Solution

Land registry is the right kind of use case valid for Blockchain implementation, and most Blockchain protocols have tried their hands at this use case. The beauty of Blockchain is that once stored, data can’t be modified or deleted, which gives Blockchain and distributed ledger technology (DLT) the edge over any other traditional database. Let’s find out how to trace out all the previously recorded ownership from Corda DLT.

As shown in Figure 4-1, let’s create a decentralized application based on Corda DLT where traceability can be closely monitored by the underlying sharable ledger.

Let’s say the different parties in this kind of deal are as follows:
  • Land department

  • Third-party surveyor

  • Bank

  • Insurance company

../images/475793_1_En_4_Chapter/475793_1_En_4_Fig1_HTML.jpg
Figure 4-1.

Land registry system architecture

The transaction flows will be as follows:
  1. 1.

    The buyer and seller approach the land department, which creates a new land registry case with existing property ID. If the property is a first-time property, then the property ID has to be generated before processing the deal.

     
  2. 2.

    The land department assigns the property details to a third-party surveyor to survey the details of the property.

     
  3. 3.

    The surveyor approves and sends back to land department or rejects with reasons.

     
  4. 4.

    Upon positive response from the surveyor, the land department sends details to the bank for successful initiation of mortgage.

     
  5. 5.

    The bank approves and sends back to the land department or rejects with reasons.

     
  6. 6.

    Upon positive response from the bank, the land department transfers the ownership from seller to buyer.

     

We can add more parties in real life such as insurance company, water/gas/electricity departments for utility connections, and so on. Please note that we can join this with a B2C web application where the buyer and seller will be registered through some off-chain database like Oracle, mysql, or mongo, where there has to be a stringent KYC process for background verification and user IDs are created against each of them. The area elaborated in the preceding covers only the B2B transactions and uses those IDs of buyer and seller provided through the off-chain database.

In Corda, the order of execution is as follows:

API class -> Flow class -> State class & Contract class -> Data committed to ledger.

However, the queries do not need to follow this path. We can directly write queries in the API layer itself. So in our example we have an execution order for new property as follows:

PropertyTransferApi.initiatePropertyTransaction()-> InitiatePropertyFlow

-> PropertyDetails -> PropertyDetailsSchemaV1

-> PropertyContract

Let’s elaborate this further.

Note

You can download the code from the repository associated with the book.

First we have to create a state class PropertyDetails with all the required data (propertyId, propertyAddress, propertyPrice, buyerId, sellerId, isMortgageApproved, isSurveyorApproved, owner, description, updatedBy, updatedTime, etc.).

In Listing 4-1, you can find the code for PropertyDetails.java. Note this class is of type LinearState. In LinearState, as we discussed before, there is a linearId of type UniqueIdentifier whose value remains the same, just like a primary key in a table which helps to track the object and its change of states. This state also implements QueryableState , which helps us to do many fine-tuned queries on the local data stored in the node in the form of a table.
package com.landRegistry.states;
import com.google.common.collect.ImmutableList;
import com.landRegistry.schema.PropertyDetailsSchemaV1;
import net.corda.core.contracts.LinearState;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.Party;
import net.corda.core.schemas.MappedSchema;
import net.corda.core.schemas.PersistentState;
import net.corda.core.schemas.QueryableState;
import net.corda.core.serialization.ConstructorForDeserialization;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
public class PropertyDetails implements LinearState, QueryableState {
    private final int propertyId;
    private final String propertyAddress;
    private final int propertyPrice;
    private final int buyerId;
    private final int sellerId;
    private final boolean isMortgageApproved;
    private final boolean isSurveyorApproved;
    private final Party owner;
    private final String description;
    private final String updatedBy;
    private final String updatedTime;
    private final UniqueIdentifier linearId;
    public PropertyDetails(int propertyId,
                           String propertyAddress, int property
                           Price, int buyerId, int sellerId,
                           boolean isMortgageApproved, boolean
                           boolean isSurveyorApproved, boolean
                           isSurveyorApproved, Party owner,
                           String description, String updatedBy,
                           String updatedTime) {
        this.propertyId = propertyId;
        this.owner = owner;
        this.propertyAddress = propertyAddress;
        this.propertyPrice = propertyPrice;
        this.buyerId = buyerId;
        this.sellerId = sellerId;
        this.isMortgageApproved = isMortgageApproved;
        this.isSurveyorApproved = isSurveyorApproved;
        this.description = description;
        this.updatedBy = updatedBy;
        this.updatedTime = updatedTime;
        this.linearId = new UniqueIdentifier();
    }
    @ConstructorForDeserialization
    public PropertyDetails(int propertyId, String property
                           Address, int propertyPrice, int
                           buyerId, int sellerId, boolean
                           isMortgageApproved, boolean
                           isSurveyorApproved, Party owner,
                           String description, String updatedBy,
                           String updatedTime, UniqueIdentifier
                           linearId) {
        this.propertyId = propertyId;
        this.owner = owner;
        this.propertyAddress = propertyAddress;
        this.propertyPrice = propertyPrice;
        this.buyerId = buyerId;
        this.sellerId = sellerId;
        this.isMortgageApproved = isMortgageApproved;
        this.isSurveyorApproved = isSurveyorApproved;
        this.description = description;
        this.updatedBy = updatedBy;
        this.updatedTime = updatedTime;
        this.linearId = linearId;
    }
    @NotNull
    public List<AbstractParty> getParticipants() {
        return ImmutableList.of(owner);
    }
    public PropertyDetails transfer(Party newOwner) {
        return new PropertyDetails(propertyId, propertyAddress,
                propertyPrice, buyerId, sellerId, isMortgage
                Approved, isSurveyorApproved, newOwner,
                description, updatedBy, updatedTime, linearId);
    }
    public PropertyDetails approvedByBank(boolean isApproved) {
        return new PropertyDetails(propertyId, propertyAddress,
                propertyPrice, buyerId, sellerId, isApproved,
                isSurveyorApproved, owner, description, updatedBy,
                updatedTime, linearId);
    }
    public PropertyDetails approvedBySurveyor(boolean isApproved) {
        return new PropertyDetails(propertyId, propertyAddress,
                propertyPrice, buyerId, sellerId, isMortgage
                Approved, isApproved, owner, description,
                updatedBy, updatedTime, linearId);
    }
    public Party getOwner() {
        return owner;
    }
    public int getPropertyId() {
        return propertyId;
    }
    public String getPropertyAddress() {
        return propertyAddress;
    }
    public int getPropertyPrice() {
        return propertyPrice;
    }
    public int getBuyerId() {
        return buyerId;
    }
    public int getSellerId() {
        return sellerId;
    }
    public boolean isMortgageApproved() {
        return isMortgageApproved;
    }
    public boolean isSurveyorApproved() {
        return isSurveyorApproved;
    }
    public String getDescription() {
        return description;
    }
    public String getUpdatedBy() {
        return updatedBy;
    }
    public String getUpdatedTime() {
        return updatedTime;
    }
    @NotNull
    public UniqueIdentifier getLinearId() {
        return linearId;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PropertyDetails that = (PropertyDetails) o;
        return propertyId == that.propertyId &&
                propertyAddress == that.propertyAddress &&
                propertyPrice == that.propertyPrice &&
                owner.equals(that.owner) &&
                buyerId == that.buyerId &&
                sellerId == that.sellerId &&
                isMortgageApproved == that.isMortgageApproved &&
                isSurveyorApproved == that.isSurveyorApproved &&
                description.equals(that.description) &&
                updatedBy.equals(that.updatedBy) &&
                updatedTime.equals(that.updatedTime) &&
                linearId.equals(that.linearId);
    }
    @Override
    public int hashCode() {
        return Objects.hash(propertyId, propertyAddress, buyerId,
                sellerId, isMortgageApproved, isSurveyorApproved,
                owner, description, updatedBy, updatedTime,
                linearId);
    }
    @NotNull
    @Override
    public Iterable<MappedSchema> supportedSchemas() {
        return ImmutableList.of(new PropertyDetailsSchemaV1());
    }
    @NotNull
    @Override
    public PersistentState generateMappedObject(MappedSchema schema) {
        if (schema instanceof PropertyDetailsSchemaV1) {
            return new PropertyDetailsSchemaV1.PersistentPropertyDetails(
                    this.propertyId,
                    this.propertyAddress,
                    this.propertyPrice,
                    this.buyerId,
                    this.linearId.getId());
        } else {
            throw new IllegalArgumentException("Unrecognised schema $schema");
        }
    }
}
Listing 4-1

PropertyDetails implementing LinearState and QueryableState Interfaces

You can find two methods named supportedSchemas() and generateMappedObject(), which are default methods and must be implemented for QueryableState.

Now in Listing 4-2, let’s check the details of PropertyDetailsSchemaV1.java.
package com.landRegistry.schema;
import com.google.common.collect.ImmutableList;
import net.corda.core.schemas.MappedSchema;
import net.corda.core.schemas.PersistentState;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.UUID;
/**
 * An PropertyDetails Schema.
 */
public class PropertyDetailsSchemaV1 extends MappedSchema {
    public PropertyDetailsSchemaV1() {
        super(PropertyDetailsSchemaV1.class, 1, ImmutableList.of(PersistentPropertyDetails.class));
    }
    @Entity
    @Table(name = "land_registry_states")
    public static class PersistentPropertyDetails extends PersistentState {
        @Column(name = "propertyId")
        private final int propertyId;
        @Column(name = "propertyAddress")
        private final String propertyAddress;
        @Column(name = "propertyPrice")
        private final int propertyPrice;
        @Column(name = "buyerId")
        private final int buyerId;
        @Column(name = "linear_id")
        private final UUID linearId;
        public PersistentPropertyDetails(int propertyId,
                                         String propertyAddress,
                                         int propertyPrice,
                                         int buyerId,
                                         UUID linearId) {
            this.propertyId = propertyId;
            this.propertyAddress = propertyAddress;
            this.propertyPrice = propertyPrice;
            this.buyerId = buyerId;
            this.linearId = linearId;
        }
        // Default constructor required by hibernate.
        public PersistentPropertyDetails() {
            this.propertyId = 0;
            this.propertyAddress = null;
            this.propertyPrice = 0;
            this.buyerId = 0;
            this.linearId = null;
        }
    }
}
Listing 4-2

PropertyDetailsSchema for persisting selected and mapped data on ledger

In PropertyDetailsSchemaV1.java, we have the flexibility of storing either all or part of the variables as original PropertyDetails object. This data is saved to a local land_registry_states table. Do not forget to implement a blank constructor, which is a basic requirement without which it would not work.

In Listing 4-3, find the contract class, which has minimal validations, but you can add all kinds of checks for business validations here.
package com.landRegistry.contracts;
import com.google.common.collect.ImmutableList;
import com.landRegistry.states.PropertyDetails;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.Contract;
import net.corda.core.identity.Party;
import net.corda.core.transactions.LedgerTransaction;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
import static net.corda.core.contracts.ContractsDSL.requireThat;
public class PropertyContract implements Contract {
    public static final String ID = "com.landRegistry.contracts.PropertyContract";
    public void verify(LedgerTransaction tx) throws IllegalArgumentException {
        verifyAll(tx);
    }
    private void verifyAll(LedgerTransaction tx) throws IllegalArgumentException {
        CommandWithParties<PropertyCommands> command = requireSingleCommand(tx.getCommands(), PropertyCommands.class);
        PropertyCommands commandType = command.getValue();
        if (commandType instanceof PropertyCommands.Create) verifyCreate(tx, command);
        else if (commandType instanceof PropertyCommands.Transfer) verifyTransfer(tx, command);
        else if (commandType instanceof PropertyCommands.BankApproval) verifyBankApproval(tx, command);
    }
    private void verifyCreate(LedgerTransaction tx, CommandWithParties command) throws IllegalArgumentException {
        requireThat(require -> {
            require.using("A Property Transfer transaction should consume no input states.",
                    tx.getInputs().isEmpty());
            require.using("A Property Transfer transaction should only create one output state.",
                    tx.getOutputs().size() == 1);
            final PropertyDetails out = tx.outputsOfType(PropertyDetails.class).get(0);
            return null;
        });
    }
    private void verifyTransfer(LedgerTransaction tx, CommandWithParties command) throws IllegalArgumentException {
        requireThat(require -> {
            require.using("A Property Transfer transaction should only consume one input state.",
                    tx.getInputs().size() == 1);
            require.using("A Property Transfer transaction should only create one output state.",
                    tx.getOutputs().size() == 1);
            final PropertyDetails in = tx.inputsOfType(PropertyDetails.class).get(0);
            final PropertyDetails out = tx.outputsOfType(PropertyDetails.class).get(0);
            require.using("The owner Property must change in a Property Transfer transaction.",
                    in.getOwner() != out.getOwner());
            require.using("There must only be one signer (the current owner) in a Property Transfer transaction.",
                    command.getSigners().size() == 1);
            return null;
        });
    }
    private void verifyBankApproval(LedgerTransaction tx, CommandWithParties command) throws IllegalArgumentException {
        requireThat(require -> {
            //Add some more checks on your own
            return null;
        });
    }
}
Listing 4-3

PropertyContract verifying input/output data in a transaction

In Listing 4-4, find InitiatePropertyFlow.java, where we can see how the new PropertyDetails object is created and added as an output state to the TransactionBuilder. We have added a setTimeWindow() to mandate the signature of the notary service within the specified time limit of 10 seconds. At the end, we find a VerifySignAndFinaliseFlow, which again calls the FinalityFlow that verifies the transaction and sends it to the notary, which checks the transaction and agrees or disagrees. Upon acceptance by the notary, the transaction is finally committed to the ledger.
package com.landRegistry.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.landRegistry.contracts.PropertyCommands;
import com.landRegistry.contracts.PropertyContract;
import com.landRegistry.states.PropertyDetails;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import net.corda.core.utilities.ProgressTracker.Step;
import java.time.Duration;
import java.time.Instant;
@InitiatingFlow
@StartableByRPC
public class InitiatePropertyFlow extends FlowLogic<SignedTransaction> {
    private final Party owner;
    private final int propertyId;
    private final String propertyAddress;
    private final int propertyPrice;
    private final int buyerId;
    private final int sellerId;
    private final String updatedBy;
    private final String updatedTime;
    private final Step GENERATING_TRANSACTION = new Step("Generating transaction based on new PropertyDetails.");
    private final Step SIGNING_TRANSACTION = new Step("Signing transaction with our private key.");
    private final Step FINALISING_TRANSACTION = new Step("Obtaining notary signature and recording transaction.") {
        @Override
        public ProgressTracker childProgressTracker() {
            return FinalityFlow.Companion.tracker();
        }
    };
    // The progress tracker checkpoints each stage of the flow and outputs the specified messages when each
    // checkpoint is reached in the code.
    private final ProgressTracker progressTracker = new ProgressTracker(
            GENERATING_TRANSACTION,
            SIGNING_TRANSACTION,
            FINALISING_TRANSACTION
    );
    public InitiatePropertyFlow(int propertyId, String property
                                Address, int propertyPrice, int
                                buyerId, int sellerId, String
                                updatedBy, String updatedTime,
                                Party owner) {
        this.propertyId = propertyId;
        this.propertyAddress = propertyAddress;
        this.propertyPrice = propertyPrice;
        this.owner = owner;
        this.buyerId = buyerId;
        this.sellerId = sellerId;
        this.updatedBy = updatedBy;
        this.updatedTime = updatedTime;
    }
    @Override
    public ProgressTracker getProgressTracker() {
        return progressTracker;
    }
    @Suspendable
    public SignedTransaction call() throws FlowException {
        Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
        progressTracker.setCurrentStep(GENERATING_TRANSACTION);
        PropertyDetails propertyDetails = new PropertyDetails(propertyId,
                propertyAddress,
                propertyPrice,
                buyerId,
                sellerId,
                false,
                false,
                owner,
                "New Property Transaction Initiated",
                updatedBy,
                updatedTime);
        progressTracker.setCurrentStep(SIGNING_TRANSACTION);
        TransactionBuilder builder = new TransactionBuilder(notary)
                .addOutputState(propertyDetails, PropertyContract.ID)
                .addCommand(new PropertyCommands.Create(), getOurIdentity().getOwningKey())
                .setTimeWindow(Instant.now(), Duration.ofSeconds(10));
        //We are adding a time window of 10 seconds for the Notary service to sign the transaction replacing default one
        //This setWindow() is completely optional
        progressTracker.setCurrentStep(FINALISING_TRANSACTION);
        return subFlow(new VerifySignAndFinaliseFlow(builder));
    }
}
Listing 4-4

InitiatePropertyFlow to start a new property workflow

Finally, in Listing 4-5 we have the PropertyTransferApi.java. In this file, we can see that the first method, getAllCurrentPropertyDetails(), retrieves all the current states of PropertyDetails objects.

The second method, getAllPropertyDetailsForId(), retrieves all of the consumed/historic states as well as the unconsumed/current states of the PropertyDetails object.

The third method, getBidOffersOfPriceRange()/actually uses QueryableState to get fine-tuned search results.
package com.landRegistry.api;
import com.landRegistry.bean.PropertyDetailsBean;
import com.landRegistry.flows.ApprovePropertyFlow;
import com.landRegistry.flows.InitiatePropertyFlow;
import com.landRegistry.flows.TransferPropertyFlow;
import com.landRegistry.schema.PropertyDetailsSchemaV1;
import com.landRegistry.states.PropertyDetails;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.CordaX500Name;
import net.corda.core.identity.Party;
import net.corda.core.messaging.CordaRPCOps;
import net.corda.core.node.services.Vault;
import net.corda.core.node.services.vault.Builder;
import net.corda.core.node.services.vault.CriteriaExpression;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static javax.ws.rs.core.Response.Status.*;
@Path("property")
public class PropertyTransferApi {
    static private final Logger logger = LoggerFactory.getLogger(PropertyTransferApi.class);
    private final CordaRPCOps rpcOps;
    private final CordaX500Name myLegalName;
    public PropertyTransferApi(CordaRPCOps rpcOps) {
        this.rpcOps = rpcOps;
        this.myLegalName = rpcOps.nodeInfo().getLegalIdentities().get(0).getName();
    }
    @GET
    @Path("getAllCurrentPropertyDetails")
    @Produces(MediaType.APPLICATION_JSON)
    public List<StateAndRef<PropertyDetails>> getAllCurrentPropertyDetails() {
        return rpcOps.vaultQuery(PropertyDetails.class).getStates();
    }
    @GET
    @Path("getAllPropertyDetailsForId")
    @Produces(MediaType.APPLICATION_JSON)
    public List<StateAndRef<PropertyDetails>> getAllPropertyDetailsForId(@QueryParam("id") String idString) {
        UniqueIdentifier linearId = UniqueIdentifier.Companion.fromString(idString);
        List<UniqueIdentifier> linearIds = new ArrayList<>();
        linearIds.add(linearId);
        QueryCriteria linearCriteriaAll = new QueryCriteria.LinearStateQueryCriteria(null,
                linearIds, Vault.StateStatus.ALL, null);
        return rpcOps.vaultQueryByCriteria(linearCriteriaAll, PropertyDetails.class).getStates();
    }
    /**
     * QueryableState query for retrieving property states of a particular price range
     */
    @GET
    @Path("getBidOffersOfPriceRange")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getBidOffersOfPriceRange(@QueryParam("priceRange") int priceRange) throws NoSuchFieldException {
        QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
        Field propertyPrice = PropertyDetailsSchemaV1.PersistentPropertyDetails.class.getDeclaredField("propertyPrice");
        CriteriaExpression statusCriteriaExpression = Builder.lessThanOrEqual(propertyPrice, priceRange);
        QueryCriteria statusCriteria = new QueryCriteria.VaultCustomQueryCriteria(statusCriteriaExpression);
        QueryCriteria criteria = generalCriteria.and(statusCriteria);
        List<StateAndRef<PropertyDetails>> results = rpcOps.vaultQueryByCriteria(criteria, PropertyDetails.class).getStates();
        return Response.status(OK).entity(results).build();
    }
    /**
     * QueryableState Experiments ends
     */
    @POST
    @Path("initiate-property-transaction")
    public Response initiatePropertyTransaction(PropertyDetailsBean propertyDetailsBean) {
        Party owner = rpcOps.partiesFromName(propertyDetailsBean.getOwnerString(), false).iterator().next();
        try {
            final SignedTransaction signedTx = rpcOps.startFlowDynamic(InitiatePropertyFlow.class,
                    propertyDetailsBean.getPropertyId(),
                    propertyDetailsBean.getPropertyAddress(),
                    propertyDetailsBean.getPropertyPrice(),
                    propertyDetailsBean.getBuyerId(),
                    propertyDetailsBean.getSellerId(),
                    propertyDetailsBean.getUpdatedBy(),
                    propertyDetailsBean.getUpdatedDateTime(),
                    owner).getReturnValue().get();
            final String msg = String.format("Transaction id %s committed to ledger.\n", signedTx.getId());
            return Response.status(CREATED).entity(msg).build();
        } catch (Throwable ex) {
            final String msg = ex.getMessage();
            logger.error(ex.getMessage(), ex);
            return Response.status(BAD_REQUEST).entity(msg).build();
        }
    }
    @GET
    @Path("transfer-department-to-surveyer")
    //We can do so for transfer-surveyer-to-department, transfer-department-to-bank, transfer-bank-to-department endpoints
    public Response transferProperty(@QueryParam("id") String idString, @QueryParam("newOwner") String newOwnerString) {
        UniqueIdentifier id = UniqueIdentifier.Companion.fromString(idString);
        Party newOwner = rpcOps.partiesFromName(newOwnerString, false).iterator().next();
        try {
            final SignedTransaction signedTx = rpcOps.startFlowDynamic(TransferPropertyFlow.class, id, newOwner).getReturnValue().get();
            final String msg = String.format("Transaction id %s committed to ledger.\n", signedTx.getId());
            return Response.status(CREATED).entity(msg).build();
        } catch (Throwable ex) {
            final String msg = ex.getMessage();
            logger.error(ex.getMessage(), ex);
            return Response.status(BAD_REQUEST).entity(msg).build();
        }
    }
    @GET
    @Path("approve-bank")
    public Response approveByBank(@QueryParam("id") String idString, @QueryParam("isApproved") boolean isApproved) {
        UniqueIdentifier id = UniqueIdentifier.Companion.fromString(idString);
        try {
            final SignedTransaction signedTx = rpcOps.startFlowDynamic(ApprovePropertyFlow.class, id, isApproved)
                    .getReturnValue().get();
            final String msg = String.format("approve-bank Transaction id %s committed to ledger.\n", signedTx.getId());
            return Response.status(CREATED).entity(msg).build();
        } catch (Throwable ex) {
            final String msg = ex.getMessage();
            logger.error(ex.getMessage(), ex);
            return Response.status(BAD_REQUEST).entity(msg).build();
        }
    }
    @GET
    @Path("approve-surveyor")
    public Response approveBySurveyor(@QueryParam("id") String idString, @QueryParam("isApproved") boolean isApproved) {
        UniqueIdentifier id = UniqueIdentifier.Companion.fromString(idString);
        try {
            final SignedTransaction signedTx = rpcOps.startFlowDynamic(ApprovePropertyFlow.class, id, isApproved)
                    .getReturnValue().get();
            final String msg = String.format("approve-surveyor Transaction id %s committed to ledger.\n", signedTx.getId());
            return Response.status(CREATED).entity(msg).build();
        } catch (Throwable ex) {
            final String msg = ex.getMessage();
            logger.error(ex.getMessage(), ex);
            return Response.status(BAD_REQUEST).entity(msg).build();
        }
    }
}
Listing 4-5

PropertyTransferApi exposing API endpoints through REST based web services

Now let’s retrieve and test this code. First, run “gradlew clean deployNodes” followed by “build\nodes\runnodes”. A series of consoles will appear, each representing a node, and this will take a few minutes to stabilize. Now using Chrome ARC, Postman, or any other REST client, send the first POST request to http://localhost:10006/api/property/initiate-property-transaction.

The request body is in Listing 4-6 and Figure 4-2.
{
  "ownerString": "LandDepartment",
  "propertyId": "1",
  "propertyAddress": "7 Bakers Street, London Postcode: AB1FG4",
  "propertyPrice": "150000",
  "buyerId": "1",
  "sellerId": "2",
  "updatedBy": "user11",
  "updatedDateTime": "01-01-2019:12.0.0"
}
Listing 4-6

Javascript Object Notation (JSON) request for initiating property transfer

../images/475793_1_En_4_Chapter/475793_1_En_4_Fig2_HTML.jpg
Figure 4-2.

Sending initiate-property-transaction POST request on REST client

Now if you run the following vault query on LandDepartment node as shown in Listing 4-7.
run vaultQuery contractStateType: com.landRegistry.states.PropertyDetails
Listing 4-7

Run Vault Query

you will get a result as in Figure 4-3.
../images/475793_1_En_4_Chapter/475793_1_En_4_Fig3_HTML.jpg
Figure 4-3.

Vault query result on node having permission

However, the other nodes for the same query will give no output, as shown in Figure 4-4.
../images/475793_1_En_4_Chapter/475793_1_En_4_Fig4_HTML.jpg
Figure 4-4.

Vault query result on node not having permission

The reason for this is the getParticipants() method, where only the owner can view the data. If your business needs other parties to view the same data, you can send the name of those parties in request and pass on to this method.

Now use the following GET request:

http://localhost:10006/api/property/getBidOffersOfPriceRange?priceRange=140000

and you will find none, whereas if you modify the priceRange as 150000 or 150001 (i.e., with a request of http://localhost:10006/api/property/getBidOffersOfPriceRange?priceRange=150000), you will find the result shown in Figure 4-5. This is a fine example of a fine-tuned query that is possible because of the implementation of QueryableState .
../images/475793_1_En_4_Chapter/475793_1_En_4_Fig5_HTML.jpg
Figure 4-5.

Fine-tuned vault query result with QueryableState interface

In the response, we can see that the owner is LandDepartment on creation. Now run a GET request as http://localhost:10006/api/property/transfer-department-to-surveyer?id=660614e8-b441-43c3-996c-1b5518f37975&newOwner=Surveyor, where ID is the linear ID of the first transaction.

Once this transaction is through running, “run vaultQuery contractStateType: com.landRegistry.states.PropertyDetails” will not show any result on the LandDepartment console, but it will show the whole state on the surveyor node, as the surveyor is now the new owner.

Now run the GET request on the surveyor node http://localhost:10010/api/property/approve-surveyor?id=6efee3cb-49a9-4558-85f0-c029f32f58e2&isApproved=true where port is 10010. You will find the “surveyorApproved” updated to true now as shown in Figure 4-6, if you vault query on the console of the surveyor node.
../images/475793_1_En_4_Chapter/475793_1_En_4_Fig6_HTML.jpg
Figure 4-6.

PropertyDetails data updated after approval of surveyor

R3 Corda Advantages

Data related to property transactions can be saved to Corda’s immutable repository, where all historical data can be tracked as and when needed. Data can be shared only among the nodes that need to know the data. This DApp can be integrated with a public Blockchain, where only required data can be shared to all users as public data.

Live Implementation

Ethereum is largely used for land registry use cases across the world. However, people have started considering Corda for the same.

References