Creating a blockchain structure using Liferay Objects
Andre Fabbro
February 20, 2024
35 Minute Read
Introduction
The Liferay platform has been used for the development of
corporate applications by many companies for several years. This is
because it stands out for its flexibility in extending its
ready-made components, broadly meeting the needs of business
applications, such as security, clustering, indexing, and internationalization.
However, directly building corporate systems on the platform can
complicate future updates due to changes in the internal API. Liferay
Inc. has, therefore, focused on features that allow extension to
external runtimes, minimizing the impact of updates on the API.
Concurrently, the platform encourages the development of low-code
applications, facilitating the updating of versions.
In this context, this article aims to explore these low-code
features of Liferay, demonstrating the construction of a blockchain
and the mining of a fictitious cryptocurrency, RayCoin, in addition to
presenting a system for managing digital wallets and transactions,
highlighting Liferay's adaptability to various business needs.
Blockchain Fundamentals
Before we proceed with the implementation using Liferay, it's
crucial to understand the fundamental concepts of the blockchain data
structure. This structure is essentially a sequence of interconnected
blocks, starting with the initial block, known as the "genesis
block". Each subsequent block maintains a reference to its
predecessor through a cryptographic hash, generated from the block's
information and including the previous block's hash. This methodology
ensures that the blockchain is a reliable and virtually immutable data structure.
The connection between consecutive blocks in the blockchain is
established through a hash, which is calculated from the information
contained in the block itself and incorporates the hash of the
preceding block. Thus, the hash of each block is derived from a
dataset that includes the current block's information along with the
previous block's hash. This approach, despite its apparent simplicity,
is profoundly ingenious, giving the blockchain its characteristic of
being a highly secure and practically immutable data structure.
Understanding the fundamental concept of blockchain, it is
essential to detail the components and attributes that make up this
structure. The central element is the Block, with the following attributes:
Index: Identifies the block's position in
the chain.
Header: Contains the calculated hash of the
block.
Previous
Hash: Refers to the hash of the
preceding block, connecting it in the chain sequence.
Nonce: A unique number
used to find the solution to the cryptographic
problem that allows the block to be mined,
adjusting to the current level of difficulty.
Transactions: A set of all
transactions included in the block, detailing
transfers of value between different wallet
addresses.
Therefore, a block not only stores its intrinsic
attributes but also encapsulates multiple transactions. These
transactions, representing debit and credit movements between
digital wallets, are fundamental to the immutability of the
blockchain, as the hash of each block is calculated by incorporating
these data. In the context of this article, in building a
cryptocurrency system, blocks contain transactions involving
RayCoin, evidencing transfers between digital wallets associated
with this fictitious cryptocurrency.
Continuing with the detailing of the blockchain elements, each
transaction within a block is defined by specific attributes that
ensure its integrity and traceability:
Transaction Id: Serves as a unique
identifier for each transaction, ensuring its uniqueness within
the chain.
Address From: Denotes the
digital wallet address from where the values will be debited,
initiating the transfer of funds.
Address
To: Specifies the transaction's destination, i.e., the
digital wallet address where the values will be credited.
Signature: Represents the digital
signature generated by the sender, essential for verifying the
transaction's authenticity and authorizing the debit.
Amount: Quantifies the value of the transaction,
expressed in the cryptocurrency's unit, facilitating the
understanding of the transferred volume.
Status: Indicates the current condition of the
transaction within the system, which can vary between states such as
approved, pending, or denied, reflecting its processing and
validation.
This detailed structuring of transactions not only reinforces
the security and transparency of the blockchain system but also
facilitates the tracking and auditing of all movements.
Finally, we address the representation of a digital wallet, a
fundamental element for blockchain users, enabling the management and
transaction of cryptocurrency units. The conception of a digital
wallet is straightforward, encompassing essential attributes for its
functionality and security:
The digital wallet is quite simple, composed of the following attributes:
Name: Informative designation of
the wallet, facilitating identification by the user.
Public Key: Acts as the address for
sending and receiving cryptocurrency units, functioning as a
public identifier of the wallet.
Private
Key: Essential for signing transactions, ensuring
the authorship and security of financial movements. This is
sensitive data, known exclusively to the wallet's owner.
This article proposes the construction of a blockchain composed
of blocks that record transfer transactions of the cryptocurrency
RayCoin between different wallet addresses. Understanding how each
block is formed, through the mining process, is crucial.
Traditionally, mining involves solving a complex cryptographic
problem that requires significant computational effort. In most
blockchain networks, the node that solves this challenge first
propagates the new block to the others, initiating a consensus
mechanism to validate the block's authenticity. If validated, the
block is added to all participants' blockchain. Several consensus
mechanisms exist, with Proof-of-Work (PoW) being one of the most
well-known. For the purposes of this article, we will focus on
constructing a single node of the blockchain, without the ambition
of developing a complete network, and, consequently, without
implementing a consensus algorithm.
The generation of blocks occurs when a defined number of
transactions is reached, triggering the mining process that
incorporates these transactions into the new block. This block is then
added to the chain, containing its own hash, calculated from the
included transactions and the previous block's hash. As part of the
mining process, units of RayCoin are credited to the miner's wallet as
a reward, a value also defined by the system. Thus, the blockchain
grows with the continuous addition of new blocks, reflecting the
received RayCoin transactions.
Project Plan
After understanding the fundamental concepts of blockchain
technology, it is imperative to establish a clear strategy for project
implementation. In this context, we will explore the resources and
functionalities provided by the Liferay platform, which will form the
basis for developing a comprehensive solution. Recognized for its
robustness and adaptability, the Liferay platform offers a series of
low-code tools that allow for the rapid construction and customization
of applications, without compromising the complexity or security
necessary for a blockchain project. Our goal is to demonstrate how
these resources can be effectively used to create a functional
blockchain structure, including the mining of a fictitious
cryptocurrency and the management of digital wallets, leveraging the
full capabilities of Liferay.
Multi-tenancy
To simulate the process of buying and selling RayCoin, the
multi-tenancy functionality of Liferay will be adopted, which allows
for the creation of multiple logical instances within a single
physical instance. This feature is crucial for structuring the
project, where two distinct instances will be configured:
RayCoin Instance: Aimed at building
the blockchain itself, this instance is responsible for
receiving transactions and mining the blocks, which will later
be organized in the chain.
XChangeRay
Instance: Designed to simulate an external service,
this instance functions as a cryptocurrency exchange, where
users can manage their RayCoin digital wallets and initiate
transactions. The goal is to facilitate user interaction with
the blockchain, allowing for the sending of transactions to the
first instance in a simplified and intuitive manner.
This functionality is a cornerstone of the low-code concept
offered by the platform, allowing for the creation of custom objects
that represent entities for the development of customized solutions.
Through Liferay Objects, the platform provides an integrated approach
to all the necessary layers for the persistence of these objects in
the context of business logic, including the automatic generation of
an integration layer via RestFul or GraphQL protocols. The Liferay
Objects framework stands out for its versatility and power, enabling a
wide range of customizations and extensions.
In this project, the objects to be created to compose our
blockchain solution include:
Blockchain: Object that
encapsulates the main information of the blockchain.
Block: Represents the individual blocks of
the chain, containing transactions, their unique hash, and the
reference to the hash of the previous block.
Transaction: Used to record transactions and
associate them with the corresponding blocks after mining.
Wallet: Allows users to create and manage
their digital wallets, essential for signing the transactions
sent to the blockchain.
Wallet
Balance: Stores the balances of wallets, facilitating
the consultation of balances without the need to traverse the
entire block chain for transaction validation.
Implementation
After an initial understanding of the main functionalities that
will support the implementation, we are ready to begin the project
setup. The first step involves activating a Liferay instance in a
local environment. For this article, we will use Liferay version
GA109. The instance can be easily set up in a Docker environment using
the following command:
docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.109-ga109
In this project, we intend to create two logical instances in
Liferay, each operating under a distinct Virtual Host, thereby
allowing each instance to respond to a specific address:
RayCoin Instance: Will be the core
of the Blockchain, responsible for processing transactions
and mining blocks, forming the block chain.
XChangeRay Instance: Will simulate an external
service, functioning as a cryptocurrency exchange, where users can
manage their RayCoin digital wallets and execute transactions.
To configure the Virtual Hosts in the local environment, it is
necessary to edit the /etc/hosts file (Linux) or
C:/Windows/System32/Drivers/etc/hosts (Windows),
including the following line:
127.0.0.1 raycoin.local xchangeray.local
This directs accesses to the addresses
http://raycoin.local:8080/ or http://xchangeray.local:8080/ to
http://localhost:8080/, allowing the running Liferay physical
instance to recognize and load the appropriate logical instance
based on the accessed Virtual Host. With Liferay active, accessing
http://raycoin.local:8080/, the main instance will be loaded. After
logging into this instance, navigate to Control Panel -> Virtual
Instances, and in the instance with Web Id equal to
"liferay.com", edit the Virtual Host to include "raycoin.local".
Next, proceed with creating a new virtual instance, entering the
necessary information:
Web Id: xchangeray
Virtual Host:
xchangeray.local
Mail Domain: liferay.com
Virtual
Instance Initializer: Blank Site
XChangeRay Instance
Wallet Object
After creating the new instance, access
http://xchangeray.local:8080/ and log in as an administrator, then go
to the menu Control Panel -> Objects to create the first object.
Access the option to add a new custom object (button '+') and include
the Wallet object information:
Label: Wallet
Plural Label: Wallets
When creating a new Object in Liferay, we are essentially
defining the records that will compose this Object. Initially,
Liferay keeps these definitions in a draft state, allowing
adjustments and refinements until the user finalizes the settings
and opts to publish the Object.
Upon accessing the panel of the newly created Object, a range of
tabs becomes available for exploration: Details, Fields,
Relationships, Layouts, Actions, Views, Validations, and State
Manager. Each of these tabs unlocks distinct features, offering
substantial flexibility for composing complex solutions. For example,
in the Fields tab, it's possible to create custom fields that
specifically align with the business logic of the application. This
level of customization emphasizes the power and versatility of the
Objects framework within Liferay.
To create a custom field, go to the Fields tab, trigger the
'+' button (Add Object Field) and fill in the field information.
Therefore, for this Wallet object, create the following custom fields:
Label
Field Name
Type
Mandatory
Name
name
Text
Yes
Private Key
privateKey
Long Text
No
Public
Key
publicKey
Long Text
No
When defining custom fields for the Wallet object, it
becomes essential to establish a logic for the automatic generation
of public and private keys every time a new Wallet is created. This
functionality is managed by the "Actions" tab of the object,
allowing the insertion of business logics at different stages of the
object's lifecycle, such as its creation or update.
Specifically for the Wallet object, it is necessary to generate
a new private key and a public key at the act of creating the
object. For this, go to the "Actions" tab, trigger the
"Add Object Action" button, filling in the "Action
Label" field with "SetKeys".
Navigate to the "Action Builder" tab. Here, we define
the logic to be executed, choose "On After Add" in the
"Trigger" option so that the action occurs immediately after
creating a new record.
Select the "Groovy Script" option for the
"Action" section. This Groovy script, leveraging the
available attributes of the object, executes the desired logic,
including the generation of keys.
Upon selecting "Groovy Script," note that a field
opens up for including the script.
To generate the keys, we will implement in this script a class
named Wallet, as below:
With this class, when a new Wallet object is created, the method
generateKeyPair() will automatically be called, generating the KeyPair
using classes from the java.security package. Then, the methods
getPrivateKey() and getPublicKey() will return the private and public
keys, respectively, already in string format, to be stored in the
corresponding fields of the object.
To store the keys in the object, we will use the
updateObjectEntry() method from the ObjectEntryLocalServiceUtil class
of Liferay's Objects API. The code should be as follows:
From this code, it's observable that the object fields can be
passed through a Map, in this case, entryValues, where we include the
public and private keys for the object with the id that is in the
context (variable id). The final script should be as follows:
Copy the code and add it to the script field, and then after
saving, you will notice that the new Action has been included in the
Wallet object.
Next, go to the Details tab and change the following fields:
Entry Title Field: Name
Panel Link: Custom
Apps
Disable the "Enable Categorization of Object entries"
option, and then click the "Publish" button. This will
publish the object. Note that Liferay already creates a default view
of the object in the Applications -> Custom Apps menu option, as
selected in the Details tab of the object.
Upon entering this option, click the "Add Wallet" button.
Fill in a name, leave the Private Key and Public Key fields
empty, and click the "Save" button.
Note that when adding a new record, the Groovy script code is
triggered, generating the public and private keys for the Wallet.
Now let's add the wallets widget so that site users can create
their own wallets. For this, go to the main site of XChangeRay and add
a new page called "My Wallets" through the Site Builder
-> Pages menu.
Next, in the page editing screen, select the "Widgets"
tab from the left sidebar, go to the "Objects" category, and
add the Wallets widget to the page.
Now, to make the page visible to site users, go to the
"Permissions" option of the "My Wallets" page and
remove the "View" permission for "Guest" users,
then add the "View" permission for "User".
This way, only registered site users can view this page. Now, it
is still necessary to add permission for authenticated users to
create their own wallets. For this, go to the Control Panel ->
Roles menu, select the "Users" option. Next, select the
"Define Permissions" tab, search for "Wallets",
and select the "View" permission under the
"Application Permissions" category and "Add Object
Entry" under "Resource Permissions", then click "Save".
When creating digital wallets in Liferay, the platform
automatically links each wallet to its corresponding user, ensuring
that only the owner can view and manage their wallets. This access
control is an integrated functionality of Liferay's Objects,
significantly simplifying permission management.
Try creating a new user and access the "My Wallets"
page to add digital wallets. You will observe that, when creating a
new wallet, it is exclusively associated with the creating user,
reinforcing security and privacy. If a second user is added to the
system, they will not have visibility or access to the first user's
wallets. Thus, each user maintains complete control over their own
wallets, being able to create, edit, and remove records as needed,
without interference or visibility from other users.
This method of direct association between users and their
digital wallets highlights Liferay's approach to data security and
customizing the user experience, facilitating the implementation of
complex functionalities intuitively and securely.
Transaction Object
After creating the Wallet object, the next step is to establish
the Transaction object, which consolidates the execution of
transactions of RayCoin units from one wallet to another. This
process will allow the user to select one of their wallets for debit
and specify the recipient wallet's address for credit. To facilitate
this operation, we will create a new object named
"Transaction". It is important to note that the recipient
wallet may not belong to the user conducting the transaction.
To add a layer of security and authentication to the
transactions, we will implement a Groovy script within the
"Actions" tab of the Transaction object. This script will
be responsible for digitally signing the transaction using the
private key of the wallet selected by the user. After the signature,
the transaction will be sent to another Liferay instance via the
Restful API, ensuring the integrity and truthfulness of the operation.
Therefore, select the option to create a new object called
Transaction. Add the following custom fields to this object:
Label
Field Name
Type
Mandatory
To Address
toAddress
Long Text
Yes
Amount
amount
Precision Decimal
Yes
Signature
signature
Long Text
No
Next, we will link the Transaction object to the Wallet object,
so that the user can select from which wallet they wish to have
their RayCoins debited. Select the "Relationships" tab,
then "Add Object Relationship," and fill in with the
following information:
Label: Wallet
Name: wallet
Type: One
to Many
One Record Of: Wallet
Many Records Of:
Transaction
Note that upon saving, a new field named Wallet will appear in
the "Fields" tab. Edit this field to set the
"Mandatory" option to true, which will require the user to
select a source wallet when creating a new transaction.
Next, select the "Actions" tab and add a new action
named "Process Transaction," select the trigger type
"On After Add" and action type "Groovy Script"
as was done during the creation of the Wallet object. In this
script, we first create a Wallet class that retrieves a wallet from
the "walletId" parameter and stores the public and private
keys, as below:
This class is designed to encapsulate the details of a
transaction, receiving in its constructor essential parameters for
executing a RayCoins transfer:
toAddress: The destination address
where the RayCoins will be credited.
amount: The quantity of RayCoins to be
transferred.
wallet: The source wallet
from where the RayCoins will be debited.
The sign() method is responsible for implementing the necessary
logic to sign the transaction. It uses the private key of the source
wallet, based on the transactionData, which compiles the essential
data of the transaction: the source address, destination address, and
the amount to be transferred. This approach ensures the authenticity
of the transaction by encrypting the data with the private key of the
source wallet.
After generating the signature, it's crucial to incorporate it
into the previously retrieved Transaction object:
// Create Wallet and Transaction objects
Wallet wallet = new Wallet(r_wallet_c_walletId)
Transaction transaction = new Transaction(toAddress, amount, wallet)
// Sign the transaction
transaction.sign()
// Update the object entry with the new signature
def userId = Long.valueOf(creator)
ObjectEntryLocalServiceUtil.updateObjectEntry(userId, id, [signature: transaction.signature], new ServiceContext())
Note that there are variables corresponding to the
Object fields that are made available in the context of the
script, such as r_wallet_c_walletId, which corresponds to the
identifier of the Wallet object selected by the user for the
transaction creation. This field is used by the Wallet class to
retrieve the related record and then retrieve the privateKey that
will be used in generating the signature. This signature will also
be validated by the raycoin instance when it is sent. At the end of
the algorithm, the ObjectEntryLocalServiceUtil class is used to
store the signature in the corresponding field of the created object.
After saving the script, select the Details tab, choose
"Custom Apps" in the "Panel Link" option, disable
the "Enable Categorization of Object entries" option, and
then publish the object. Note that the Transactions option will now be
available in the Applications -> Custom Apps menu.
Add a new Transaction and see that the Signature field will be
automatically generated.
Similar to the process of creating the Wallet object, it is
necessary to add a new page called "My Transactions" on the
XChangeRay site. This step includes configuring appropriate
permissions for the User Role, allowing the addition of new entries.
Subsequently, the Transactions widget should be incorporated into the
new page, analogous to what was done on the "My Wallets"
page. This way, users will be able to enter their transactions directly.
Up to now, transactions created on the XChangeRay instance are
not sent to the Blockchain (RayCoin) instance, due to the absence of
the necessary objects in the other instance. However, it is possible
to anticipate and prepare the code for sending transactions. This is
done by editing the Groovy script of the Transactions object,
incorporating the sendToBlockchain(postUrl) method into the
Transaction class:
This method performs a POST request to the specified endpoint in
"postUrl," which will correspond to the Transaction object's
endpoint in the RayCoin instance. This endpoint, which will be
detailed in subsequent sections of this article, is anticipated as:
"http://raycoin.local:8080/o/c/transactions/". Therefore, at
the beginning of the script, include the call to the sendToBlockchain
method, passing the URL of the endpoint:
After setting up the XChangeRay instance with its objects, it's
time to turn our attention to the main instance, RayCoin, to configure
the objects that will form the backbone of our business logic. Access
the URL http://raycoin.local:8080/ and log in as an administrator. In
the Control Panel, find and select the "Objects" option to
start creating the necessary objects for this instance. The objects
and their fields should be configured as follows:
Block:
Label
Field Name
Type
Mandatory
Index
index
Integer
Yes
Previous Hash
previousHash
Long Text
Yes
Nounce
nounce
Integer
Yes
Transaction:
Label
Field Name
Type
Mandatory
From Address
fromAddress
Long
Text
Yes
To Address
toAddress
Long Text
Yes
Amount
amount
Precision
Decimal
Yes
Signature
signature
Long Text
Yes
Signature Valid
signatureValid
Boolean
No
Wallet Balance:
Label
Field Name
Type
Mandatory
Address
address
Long Text
Yes
Balance
balance
Precision Decimal
Yes
Blockchain:
Label
Field Name
Type
Mandatory
Name
name
Text
Yes
Max Pending Transactions
maxPendingTransactions
Integer
Yes
Reward Address
rewardAddress
Long Text
Yes
Reward Value
rewardValue
Precision Decimal
Yes
Authorization
authorization
Long
Text
Yes
Blockchain
URL
BlockchainURL
Text
Yes
For each object created, access the "Details" tab,
adjust the "Custom Apps" value in the "Panel
Link" option, disable "Enable Categorization of Object
entries," and finally, publish the object by clicking on "Publish".
Relationships
After establishing the objects, it's necessary to configure the
relationships between them, simulating the links of a database. In
Liferay, these relationships can be many-to-many or one-to-many. For
our application, a Blockchain object will contain multiple Blocks, and
each Block, in turn, will house multiple Transactions. To configure
these relationships, edit the Transaction object, select the
"Relationships" tab, and add a new relationship with the information:
Label: Block
Name: block
Type: One to
Many
One Record Of: Block
Many Records Of:
Transaction
Go back to the "Fields" tab and note that a new field
named "Block" has been created, with the type
"Relationship". As transactions initially do not belong to
any block when they are recorded, this field does not need to be mandatory.
Now go to the Block object's editing screen, navigate to the
"Relationships" tab, and you will see that the relationship
created in the Transaction object also appears on this screen.
Click the "+" button to then include the relationship
between the Block and the Blockchain, fill in the following information:
Label: Blockchain
Name: blockchain
Type: One to Many
One Record Of: Blockchain
Many Records Of: Block
Now in the "Fields" tab, see the
"Relationship" type field named Blockchain, change this
field to make it mandatory by marking the "Mandatory" field.
Access Permissions
Finally, it is crucial to set access permissions to determine
which objects will be public for reading and/or writing. This
setting is vital, especially to allow external systems, such as
XChangeRay, to interact with the blockchain by creating Transactions
and querying Blocks and Wallet Balances. Access the "Control
Panel" menu, go to "Roles," select "Guest"
under "Regular Roles," and in the "Define
Permissions" tab, search for the term "Transactions,"
and check the "Add Object Entry" option in the
"TRANSACTIONS" category and "View" in the
"TRANSACTION" category, both under "RESOURCE PERMISSIONS".
After saving, search for the term "Blocks" and select
only the "View" option in the "BLOCK" category
also under "RESOURCE PERMISSIONS".
Next, do the same procedure for "Wallets Balances,"
marking only the "View" option.
Initial Objects Data
After creating the objects, their relationships, and access
control through the permission system, we have obtained the
fundamental structure of the Blockchain. Let's now create the first
records of these Objects, which will serve as the basis for the next
data that will be received by the API.
Navigate to "Applications" and choose "Custom
Apps," select "Blockchains," and use the "+"
button to add a new record. Fill in the information as follows:
Authorization: Use for
authentication in calls to the Liferay RestFul API for
non-public methods. For example, to check the need for
mining after a transaction. Use "Basic"
authentication by converting the username and password to
base64. For example, for the user 'test@liferay.com' with
password 'test', the value will be "Basic
dGVzdEBsaWZlcmF5LmNvbTp0ZXN0".
BlockchainURL: Enter the endpoint of the RayCoin
instance, such as "http://raycoin.local:8080".
Max Pending Transactions: Define the number of
pending transactions required to start the mining process, for
example, "3".
Reward
Address: Use the public key of a wallet generated for
the miner, for example, the public key can be
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKutm5pE/nTelGk9p2mk9HpULdy3ID2tWTx/2O+D65dsAzA1MQNSdfF6RwGnCZfik++V6trwdCPTmomz0rWD73g==".
Reward Value: Set the mining reward value,
such as "10".
After saving this record, proceed to the "Blocks"
object and add the initial block, the genesis block, with the
following information:
Hash: Use "0" for the initial hash.
Index: Position "0" for the first block.
Nonce: Value "0", indicative of being the
genesis.
Previous Hash: Enter "none," since it is
the first block.
Blockchain: Select the previously created
"RayCoin" option.
Having done that, let's now create a record for
"Transaction," representing the initial transaction with an
allocation of 500,000 RayCoins to a specific wallet, let's use the
following wallet in this example:
Public Key:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8QK/c9ZSIcVMN8HYfPcZ2cW/CXfN7c8hqFdazgE1lsFmes8UeIVovMgivL2qyeZAOuECrE8eg7HixuYcwhNFOw==
For this, create a new record in "Transactions" with
the following attributes:
Amount: 500000
From Address: none
Block: 0
Signature: 0
To Address:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8QK/c9ZSIcVMN8HYfPcZ2cW/CXfN7c8hqFdazgE1lsFmes8UeIVovMgivL2qyeZAOuECrE8eg7HixuYcwhNFOw==
Object Actions
After establishing the fundamental structure and initial
records, we are ready to integrate essential business logic through
scripts associated with the "Actions" functionality of the
objects. For each type of object involved, we will develop four
scripts, detailed below:
Blockchain Object:
Mine Pending Transactions: This
action, configured as standalone in the Blockchain object,
diverges from actions linked to the object's lifecycle, such as
the one implemented for the Wallet object in XChangeRay.
Instead, this action becomes available for activation via API.
Its primary function is to check if the number of pending
transactions is sufficient to initiate a mining process. If
affirmative, it proceeds to mine a new block that, once
validated, is added to the blockchain.
Compute Balances: Also a standalone action in the
Blockchain object, aimed at updating the Wallet Balance object
records. It calculates the balance of each wallet, aggregating this
data to facilitate future balance queries.
Transaction Object:
Validate Transaction: This action
is triggered when a new Transaction is created. Its purpose
is to validate the transaction's signature and verify if the
originating wallet has sufficient funds to carry out the
transaction.
Trigger Blockchain: This
action, also triggered upon the creation of a new Transaction,
aims to activate the Mine Pending Transactions method of the
Blockchain object. This method assesses whether it is the
appropriate moment to start the mining process.
Validate Transaction
To begin implementing the "Validate Transaction"
functionality, go to the "Objects" section in the Control
Panel, navigate to "Object -> Objects," select the
"Transaction" object and proceed to the "Actions"
tab. Add a new action named "Validate Transaction." In the
"Action Builder" interface, choose "On After Add"
in "Trigger" and select "Groovy Script" in
"Action" to start drafting the script.
Initially, we will create a class called TransactionValidator,
incorporating attributes that mirror those of the
"Transaction" object, in addition to support attributes. The
initial structure of the class will be:
Next, we will develop a method within this class to validate the
signature associated with the object, using classes from the
java.security package, available in the JVM. This process is similar
to the one used for generating public and private keys and signing in
the XChangeRay instance. However, in this context, we focus on validation:
void validateSignature() throws Exception {
if (this.fromAddress == 'none') {
return
}
byte[] signatureBytes = Base64.getDecoder().decode(this.signature)
if (signatureBytes == null || signatureBytes.length == 0) {
throw new RuntimeException("No signature found in this transaction")
}
def keyFactory = KeyFactory.getInstance("EC")
def publicKeySpec = new X509EncodedKeySpec(Base64.decoder.decode(this.fromAddress))
def publicKey = keyFactory.generatePublic(publicKeySpec)
def signatureInstance = Signature.getInstance("SHA256withECDSA")
signatureInstance.initVerify(publicKey)
signatureInstance.update(this.transactionData.bytes)
if(!signatureInstance.verify(signatureBytes)) {
throw new Exception("Signature validation failed")
}
}
Then, we will develop a method to verify if the originating
wallet has a sufficient balance for the transaction. This process
requires querying the "Wallet Balance" object to access
the balances processed by the blockchain. We will use the
"Blockchain" object identifier to find the appropriate
address for the HTTP call, retrieving the URL of the RayCoin
instance and completing it with the specific endpoint for the
"Wallet Balances" API.
First, it's necessary to access the information contained in the
"Blockchain" object, which stores the RayCoin instance URL.
This is done through the ObjectDefinitionLocalServiceUtil class,
allowing the retrieval of the "Blockchain" object definition
identifier with the following code:
With this identifier in hand, we access all records of the
"Blockchain" type and select the first one, considering the
existence of only one record of this type:
However, this value represents only the beginning of the
endpoint. To complete it, it's necessary to add the specific path of
the API that manages the "Wallet Balance" objects,
resulting in /o/c/walletbalances. To discover the available
endpoints for each object created on the Liferay platform, the
platform offers an API catalog accessible at /o/api/. By visiting
http://raycoin.local:8080/o/api/, it's possible to explore and select
the desired object, thus obtaining detailed information about the
available endpoints.
This Liferay feature is extremely powerful as it allows the
platform to be manipulated in a consistent and flexible manner,
enabling both read and write operations, and also allowing the
application of filters, sorting, searching, among other features. With
this, developers can create remote applications in any technology and
use the API as part of their business rules.
Therefore, to validate the balance, the script should contain a
GET call to the "Wallet Balance" object endpoint, using a
filter by the "Address" field to identify the balance of
the originating wallet. The result of this call is processed to
verify the availability of sufficient funds for the transaction:
After the execution of the call, if the wallet does not have
sufficient funds, an exception will be thrown, indicating insufficient
balance. This process ensures that only valid transactions are
processed on the blockchain:
JsonArray walletBalancesJsonArray = getResponseJson.getJsonArray("items")
if(walletBalancesJsonArray.empty) {
throw new Exception("Wallet doesn't have enough funds")
} else {
JsonObject walletBalance = walletBalancesJsonArray.getJsonObject(0)
def balance = new BigDecimal(walletBalance.getJsonNumber("balance").toString())
if(amount > balance) {
throw new Exception("Wallet doesn't have enough funds")
}
}
It is now also necessary to create a method to update the
transaction status. We will use the "status" field, a
standard attribute in all object definitions in Liferay. It's
important to recognize that changing the status should occur after the
record creation, as by default, all objects are initially marked as
"APPROVED" in the absence of a defined workflow. To simplify
and avoid the complexity of introducing a workflow process, we opt to
modify the status asynchronously, with a delay of three seconds after
including the record in the system. The implementation of this method
is described below:
void updateTransactionStatus(userId, id, status) {
Thread.start {
sleep(3000) // Sleep for 3 seconds
ObjectEntryLocalServiceUtil.updateStatus(userId, id, status, new ServiceContext())
}
}
With the TransactionValidator class now complete with all
necessary methods, we can proceed with the transaction validation
logic as follows:
Transaction Origin Validation: Initially, we check if the
source address is 'none', which would indicate a reward transaction,
exempting it from validations.
Creation of the
TransactionValidator Object: Next, we instantiate the
TransactionValidator object, filling its essential attributes with
the transaction data.
Validation Process: Within a
try-catch block, we perform the signature validation and wallet
balance check. If both validations are successful, we update the
transaction status to 'PENDING'. If any validation fails, the status
is changed to 'DENIED'.
try {
// first, set the signatureValid to false
ObjectEntryLocalServiceUtil.updateObjectEntry(userId, id, [signatureValid: false], new ServiceContext())
// then, validate the signature
transactionValidator.validateSignature()
// set the signatureValid to true
ObjectEntryLocalServiceUtil.updateObjectEntry(userId, id, [signatureValid: true], new ServiceContext())
// validate if the wallet has enough funds for this transaction
transactionValidator.validateBalance()
// set the transaction status to pending
transactionValidator.updateTransactionStatus(userId, id, WorkflowConstants.STATUS_PENDING)
} catch (Exception e) {
// as the signature wasn't validated or the wallet doesn't have funds, set the status to denied
transactionValidator.updateTransactionStatus(userId, id, WorkflowConstants.STATUS_DENIED)
}
Within the same "Transaction" object, add a new Action
titled "Trigger Blockchain," setting it up with the type
"On After Add" and selecting "Groovy Script" as
the Action type. This Action will introduce the logic that, with every
new transaction received by the API, will trigger the "Mine
Pending Transactions" action on the "Blockchain"
object, which will be detailed later.
Initially, the action will check if the transaction's
originating address is 'none', indicating a reward transaction that
does not require further processing:
if(fromAddress == 'none') return
Next, the script retrieves information from the
"Blockchain" object to access the instance's URL and the
value of the "Authorization" attribute, necessary for
authentication in HTTP calls, given that the "Blockchain"
object has access restrictions:
Considering that the action on the "Blockchain" object
will be of the "standalone" type, the API call will use the
PUT method. The call's URL is constructed by concatenating the
blockchain's URL with the specific API endpoint to trigger the
standalone action:
To ensure that the call is executed asynchronously and after the
successful inclusion of the record, the script runs within a new
Thread, with an initial delay of 5 seconds:
To integrate the logic of updating balances in the Liferay
blockchain, first access the Blockchain object definition through the
control panel and add a new Action called "Compute
Balances". In this context, choose "Standalone" as the
"Trigger" type and select "Groovy Script" as the
action to be executed.
We will develop a class called BlockchainBalance, which will
incorporate essential attributes such as companyId and userId, in
addition to an additional parameter objectDefinitionId to record the
Wallet Balance object definition ID:
class BlockchainBalance {
long companyId
long userId
long objectDefinitionId
BlockchainBalance(long companyId, long userId) {
this.companyId = companyId
this.userId = userId
this.objectDefinitionId = ObjectDefinitionLocalServiceUtil.fetchObjectDefinition(
this.companyId, "C_WalletBalance").getObjectDefinitionId()
}
}
We will implement a method removeAllBalances() to clear all
balance records, preparing the system for a balance update based on
Blockchain transactions:
The computeBalance() method will be responsible for first
clearing existing balances and then calculating and recording
updated balances based on approved transactions. This includes
initializing the 'none' wallet with a significant balance for reward
distribution and updating balances according to processed transactions:
To effectively implement block mining and its inclusion in the
blockchain, we will add a new action to the Blockchain object called
"Mine Pending Transactions". This action will be of the
"Standalone" type and will use "Groovy Script" as
its execution method.
Within the script, we initially retrieve all transactions with
'PENDING' status:
We check if the number of pending transactions exceeds the limit
defined in the maxPendingTransactions attribute of the Blockchain
object. If so, we proceed to mine a new block:
Thus, the Blockchain class can start with the following attributes:
class Blockchain {
long companyId
long userId
long blockchainId
String rewardAddress
BigDecimal rewardValue
List pendingTransactions
String authorization
String blockchainURL
}
We will also need two other support classes, to handle blocks
and transactions, therefore we create a Block class:
class Block {
long blockchainId
int index
String timestamp
List transactions
String previousHash
String hash
int nonce
}
And also a Transaction class:
class Transaction {
Long id
String fromAddress
String toAddress
BigDecimal amount
String signature
}
In this Transaction class, we will add methods to return the
transaction data in the format as we have used in other classes, in
addition to the methods toJson(), toJsonArray(), and toString(), which
we will use as the basis for calculating the Block Hash:
The mineBlock() method is essential for the block mining process
in the blockchain, determined by the difficulty parameter, an
integer that defines the complexity of the task. The higher the
difficulty value, the greater the computational effort required to
find a valid hash for the block, based on the combination of various
elements like the block index, timestamp, the list of transactions,
the previous block's hash, and the nonce (an attempt counter). The
goal is to generate a hash that starts with a specific sequence of
zeros, meeting the established difficulty criterion. This mechanism,
although simplified in this example using the MD5 algorithm for hash
generation, illustrates the fundamental concept behind mining in
blockchains. In more advanced implementations, more complex
cryptographic algorithms and mathematical problems are used to enhance
the security and integrity of the chain. However, a simplified
approach was chosen in this case to facilitate understanding and
demonstration of the mining process.
Returning to the Blockchain class, we will implement a utility
method for making RestFul API calls:
It is still necessary to have two other methods, one to retrieve
the latest block in the chain, and another to retrieve a block from
its Previous Hash attribute:
After that, we will implement the final method, called
minePendingTransactions(), which will be responsible for implementing
the entire business logic. We create an internal list of Transactions
to receive the values from the list of pending transactions. Next, we
create a reward transaction, save this transaction in the database,
and also add it to the same list:
void minePendingTransactions() {
List transactions = new ArrayList()
pendingTransactions.each { pt ->
if(pt.getValues().get("signatureValid") == true) {
transactions << new Transaction(
pt.getValues().get("fromAddress"),
pt.getValues().get("toAddress"),
pt.getValues().get("amount"),
pt.getValues().get("signature"),
pt.objectEntryId)
}
}
def rewardTransaction = new Transaction("none", rewardAddress, rewardValue)
rewardTransaction.save(companyId, userId)
transactions << rewardTransaction
Then retrieve the Hash of the last block in the chain, as well
as its index, to increment it and use it as a parameter in the block
we need to create:
To ensure that the transactions are not processed by another
mining process, which may occur in parallel, we will change the
status of all transactions to 'SCHEDULED':
transactions.each { t ->
t.updateStatus(userId, WorkflowConstants.STATUS_SCHEDULED)
}
Next, we can create the new block and then execute the method
for mining, using a difficulty level of 2, meaning the generated
hash must contain two zeros at the beginning:
def block = new Block(index, new Date().toString(), transactions, previousHash, blockchainId)
def difficulty = 2
block.mineBlock(difficulty)
After the mining process, we check if another block that was
mined before this one and placed in the same position exists, if
there is, we must abandon the mining, deleting the reward
transaction, and returning the status of the transactions to 'PENDING':
To efficiently manage and update the balances of the wallets
involved in the transactions within the block mining process, we will
implement the following logic using a HashMap. This map will store the
current balances of all wallets impacted by the transactions,
facilitating the validation of sufficient funds and the correct
association to the mined block. Each transaction will be individually
assessed to determine if the originating wallet has sufficient funds
for the transaction. Otherwise, the status of the transaction will be
marked as 'DENIED'. Approved transactions will receive the status 'APPROVED':
After the successful completion of the mining process and
transaction validation, we will update the reward transaction's status
to 'APPROVED' and associate it with the mined block. To ensure data
consistency and avoid potential execution conflicts, updating the
wallet balances will be performed asynchronously, with a brief delay:
With the setup of the necessary actions complete, it is now
possible to start creating new transactions using the XChangeRay
instance. But first, go to the Blockchain in Applications -> Custom
Apps -> Blockchain menu and execute the Compute Balances method
from the Blockchain record in order to compute the initial balance of
the wallets.
After this, create some wallets and transactions from the
XChangeRay instance. This process will allow the direct observation of
how transactions are processed and integrated into the RayCoin
instance. It is recommended to start with the "XChangeRay
Wallet" as it initially holds a significant amount of coins,
facilitating the execution of test transactions. Thus, create some
wallets and transactions in this instance.
It is also possible to follow the blocks records.
And also the wallet balances through the Wallet Balances object.
For those who prefer a more direct approach and want to try the
demo showcased in this article without going through the whole
configuration process, it is feasible to start an instance of Liferay
version 7.4.3.109-ga109, connect it to a local MySQL database, and
import the DUMP available at: https://github.com/andrefabbro/blockchain-demo/tree/main/mysql-dump
This DUMP includes all the object definitions, scripts, and
sample data necessary to simulate the blockchain described in this
article, providing an efficient way to visualize the application in action.
Conclusion
The popularity of blockchain, initially spurred by the
cryptocurrency market, barely scratches the surface of its disruptive
potential. Beyond the realm of digital currencies, blockchain has a
wide array of applications in various sectors, from smart contracts,
supply chains, and logistics, to real estate registry, digital
identity, and electronic voting. These applications underscore the
innovative capability of blockchain to offer robust solutions for
traditional challenges, transforming processes in a myriad of industries.
While this article focused on exploring the development of a
blockchain utilizing the native functionalities of Liferay,
specifically through Objects, it is important to highlight that the
Liferay platform is equipped with other tools and extensions, such as
Client Extensions (available at: https://learn.liferay.com/w/dxp/building-applications/client-extensions),
enabling the implementation of additional business logics. Therefore,
Liferay presents itself as a versatile and powerful platform, capable
of supporting the development of complex solutions, allowing
developers to concentrate their efforts on the specific requirements
of each business.
This article aims not only to demonstrate the applicability of
blockchain technology in diverse contexts but also to emphasize the
flexibility of Liferay as a development tool. The possibilities are
broad and promising, paving the way for innovations that can reshape
the fabric of various sectors, enhancing efficiency, transparency, and security.
This website uses cookies and similar tools, some of which are provided by third parties (together “tools”). These tools enable us and the third parties to access and record certain user-related and activity data and to track your interactions with this website. These tools and the informationcollected are used to operate and secure this website, enhance performance, enable certain website features and functionality, analyze and improve website performance, and personalize user experience.
If you click “Accept All”, you allow the deployment of all these tools and collection of the information by us and the third parties for all these purposes.
If you click “Decline All” your IP address and other information may still be collected but only by tools (including third party tools) that are necessary to operate, secure and enable default website features and functionalities. Review and change your preferences by clicking the “Configurations” at any time.