Lightweight Contracts

From Nxt Wiki
Jump to: navigation, search
This page contains changes which are not marked for translation.


1 Introduction to Lightweight Contracts

Lightweight Contracts represent a framework to develop a layer of automation on top of the existing Ardor APIs.

Contracts are developed by implementing pre-defined interfaces. The contract code is deployed to the blockchain as cloud data transaction which stores the code itself and a contract reference transaction which provides the trigger to activate the contract and provides the setup parameters.

Learn more about the concepts behind the lightweight contracts in this introduction article and review some frequently asked questions


2 Prerequisites

Contract developers are expected to master the Java programming language, some prior knowledge of blockchain in general and Ardor specifically and concepts like account, passphrase, transaction and block are important but not mandatory as you can always review our sample contracts.


3 Getting Started

3.1 Installation

Install Java SE JDK 8 or higher.

Install the latest version of the IntelliJ IDE, the free community edition is supported. You can develop contracts using any other Java IDE but the setup provided and all example will assume using the IntelliJ IDE.

Install Ardor 2.1 or higher and make sure to check the dev.tools option during installation.

3.2 Contract Runner

Unlike many other contract development frameworks, lightweight contracts are not invoked by every node on the blockchain, instead you need to setup your node to run contracts by registering the contract runner addon. The contract runner monitors every new block for transactions which trigger a contract and when found, triggers the respective contract, it also runs contracts which execute on every block and provides APIs for triggering contracts per call.

To register the contract runner add the property nxt.addOns=nxt.addons.ContractRunner to your node's nxt.properties file

The contract runner addon relies on a json configuration file, to specify the configuration file used by the contract runner add the property addon.contractRunner.configFile for example addon.contractRunner.configFile=./addons/resources/contracts.json, the specified path is relative to the user folder.

Failing to specify or locate the configuration file will cause the contract runner to log an error starting with "Contract configuration file" and abort. The node itself will continue to run.

3.2.1 Contract Runner Configuration File Settings

A sample contract configuration file is provided in the ./addons/resources/contracts.json file under the installation folder.

"secretPhrase" - specify the secret phrase of the contract runner account i.e. the account from which the contract runner will submit transactions. This passphrase is never submitted to any remote node, however you should never use an account with a large amount of tokens as the contract runner configuration file is stored in clear text.

"accountRS" - alternatively if you only want your contract runner to verify transactions submitted by another contract runner, specify the account of the contract runner you would like to follow. This way your contract runner can repeat the contract calculation and verify transactions submitted by another contract but not submit its own transactions.

"feeRateNQTPerFXT.<Chain Name>" - when submitting transactions the contract runner will use this value specified in NQT to calculate the child chain fee to submit. For each chain your contract runner is expected to submit transactions, you need to specify this value. If no fee ratio for no chain is specified the contract runner will not start.

"params" - specify contract setup parameters per contract. Use this setting only for setup parameters you don't like to submit to the blockchain. Otherwise specify the setup parameters in the contract reference transaction as explained in the contract manager configuration. These parameters are accessed by the contract code using the context.getContractRunnerConfigParams(getClass().getSimpleName()) method.

"validator" - if set to true (default: false), the contract runner will watch for transactions submitted by other contract runners and will try to verify that the other contract runner indeed run the contract stored on the blockchain.

"catchUpInterval" - a timeout value specified in seconds (default: 3600 i.e. one hour), during blockchain download the contract runner will only submit transactions after downloading a block which its time stamp is no more in the past than the catchUpInterval. The purpose of this setting is to prevent a contract runner from flooding the unconfirmed transaction pool with duplicate transactions during blockchain download.

"seed" - supply random seed to the contract runner formatted as hex string (default: the public key of the contract account i.e. useless from randomness perspective), convert any random value to hex string using the HexConvert API and specify the hex string value as a seed. A seed of less than 16 bytes can be easily brute forced so make sure your seed is longer. Keep your seed secret, it can be used in the future to prove your contract execution.

3.3 Contract Manager

The contract manager is a command line utility which manages the life cycle of the contracts. Its primary usage is to deploy new versions of a contract, it can also be used to add a new reference to a contract and supply setup parameters and to delete existing reference.

3.3.1 Contract Manager Configuration

The settings which controls the operation of the contract manager are defined in the nxt.properties file, their documentation is specified in the conf/nxt-default.properties under the installation folder in the #### CONTRACT MANAGER #### section

Specifically specify the contract.manager.uploadParamsFile setting to point to the contract manager upload parameters configuration file.

3.3.2 Upload Parameters Configuration File

The upload parameters file contains a json array of contract definitions named "contracts".

For each contract you need to specify the following parameters:

"className": the name of the class without package name, this name represents the class name loaded by the contract runner from the data cloud and the name of the contract reference.

"packageName": the Java package name of the contract class as specified in the package directive inside the source code.

"filePath": only case the contract is deployed as a Jar file. Specify the path to the Jar file which will be deployed to the cloud data. If no filePath is specified the contract manager will attempt to load the class file itself from the classpath.

"params": a Json object representing the contract setup parameters. These parameters are accessed by the contract using the getContractParams() method.

The upload parameters for the sample contracts available out of the box are defined in the ./addons/resources/contract.uploader.json file.


4 Setting Up the Development Environment

Assuming all dependencies were installed, we can now open the Ardor contracts project inside IntelliJ and start developing contracts.

The IntelliJ project structure is provided as part of the "Contract Development Tools" package you selected when installing Ardor so there is no need to create a new project.

If you installed Ardor into a read only folder you will need to copy it to a folder for which you have write permissions.

Windows - copy "c:\Program Files\Ardor" to a writable location such as c:\Users\<Your user>\Documents

Mac - copy /Applications/ardor.app to ardor.app in your home folder (use cp -R from the terminal for recursive copy on Mac)

Linux - this should not be an issue as long as you installed Ardor into a folder you own.

Alternatively, if you decided to develop your project in the same location where you installed Ardor, a future upgrade may override some of the project files for good or bad so make a backup of your project before upgrading.

Start IntelliJ and choose the Open project option, in the resulting file dialog choose the Ardor folder itself.

Ope.project.PNG

Select the Ardor module from the left pane

Project1.PNG Project2.PNG

The Ardor module contains the sample contracts provided with installation.

Under the Ardor module there are two Java source roots, the first contains sample contracts, the second contains the unit tests for the sample contracts.

For example this is the HelloWorld sample contract.

Project4.PNG

Your IntelliJ contract contains the following launchers

Ardor Desktop - runs the node in desktop mode where resources are created in the user folder (typical for Windows and Mac)

Ardor Local - runs the node with resources located in the installation folder (typical for Linux)

Contract Manager Desktop - invokes the contract manager utility which loads resources from the user folder

Contract Manager Local - invokes the contract manager utility which loads resources from the installation folder

ContractRunnerSuite - invokes the automatic tests for all contracts

You are now ready to develop your first contract


5 Contract Development

A contract is composed of one or more Java class files possibly packaged into a Jar and possibly relying on other Jar files.

The main contract class has to extend the nxt.addons.AbstractContract class and implement one or more of the provided callback methods which are invoked by the Contract Runner based on specific events.

void processBlock(BlockContext context) - invoked every block

void processTransaction(TransactionContext context) - invoked when a trigger transaction is applied in a blockchain

void processRequest(RequestContext context) throws NxtException - invoked by calling the triggerContractByRequest API from the client

void processVoucher(VoucherContext context) - invoked when a voucher is submitted to the contract using the triggerContractByVoucher API

R processInvocation(DelegatedContext context, T data) - invoked when another contract calls this contract


5.1 Context Objects

Each of these callback methods receives a context object, The context object provides various services to the contracts and represents a bridge between the contract and the blockchain.

Always prefer using the context object over calling directly to an internal blockchain method since every method provided by the context is guaranteed to maintain backward compatibility and not break your contract code when a new Ardor release is deployed. The services provided by the context object depend on the type of callback, these services are documented in the Javadoc for the context class.

5.2 API callers

For each of the Ardor public APIs there is a specific Java caller class named the same as the API with a capital letter at the beginning and a "Call" prefix.

For example the call object for the getBlockchainStatus API is GetBlockchainStatusCall.

API invocation always follows the same pattern, create a call instance, possibly set some API parameters, invoke the call() method to receive a Json response as a JO object you can then work with.

For example: to invoke the getExecutedTransactions API from inside the contract:

GetExecutedTransactionsCall request = GetExecutedTransactionsCall.create(2).height(height).type(0).subtype(0).recipient(context.getConfig().getAccount());

JO getExecutedTransactionsResponse = request.call();

Always use the API callers to access information stored on the blockchain since these API callers represent the interface between your contract and the data stored on the blockchain so they'll remain compatible in future releases.

5.3 Submitting Transactions

To submit a transaction invoke the API caller for the specific transaction type then use the context.createTransaction() method to submit the transaction to the blockchain. Internally the createTransaction method takes care of local signing, fee calculation and other complex processing that you as contract developer don't have to deal with.

SendMoneyCall sendMoneyCall = SendMoneyCall.create(context.getChainOfTransaction().getId()).recipient(recipient).amountNQT(returnAmount);

context.createTransaction(sendMoneyCall);

5.4 Contract Parameters

For contracts triggered by a transaction use the context.getRuntimeParams() to load the specific invocation parameters.

To load the contract setup parameters specified in the contract reference transaction use the getContractParams() method of the contract itself.

To load the contract runner parameters specified in the contract runner configuration file for the specific contract use context.getContractRunnerConfigParams(getClass().getSimpleName()).

All get parameters method return a JO object.

5.5 Working with Json

All API callers and configuration parameters return a JO object which represent a Json object.

Use the JO object to query the Json object. Specifically use the getArray() method to obtain a JA object representing a Json Array.

There are plenty of usage examples in the sample contracts.

5.6 Accessing External Resources

Unlike most smart contract frameworks, lightweight contract support access to external resources. Your contract can interface with any service as client as long as it provides a Java client API or an Http interface.

If necessary your contract can access code from 3rd party Jar files as long as these Jar files are included in the classpath of the contract runner node.

Learn mode about Oracle Contracts

5.7 Lightweight Contracts Design

To learn more about the internal design of contract see this article


6 Best Practices

6.1 Parameter Validation

Always start your contract with some validity checks on the input. For example if your contract is triggered by a payment, make sure this was made to the contract account.

If it expects a certain amount, make sure this amount was received.

Validate that all invocation parameters represent the data type you expect and are within valid boundaries.

6.2 Stateless Contracts

In most cases, contracts should only store state information in the blockchain itself or as setup parameters. If you are using member variables in the contract class itself you are might be doing something wrong. Bare in mind that the contract runner might be restarted and that contract callbacks may execute in parallel. Learn more about stateless contracts

6.3 Internal Blockchain Functionality

Do not invoke public methods of the blockchain directly. Always attempt to use the context object and the caller APIs.

If you are missing some essential function or services, let us know and we will add it in the next release.

6.4 Random Data

If your contract requires random data, make sure to define a secret random seed in the contract runner configuration. See the following article about our approach to random number generation


7 Contract Deployment

As explained above, contract deployment is a two step process, first the contract code is deployed using a cloud data transaction then a contract reference transaction is submitted to provide an entry point to the correct version of the contract. While this deployment process can be accomplished manually using the Ardor API, it is much simpler to perform the deployment using the contract manager utility.

First configure the contract manager then invoke ContractManager.bat or contractManager.sh from the command line to view the available options, or invoke one of the contract manager launchers from the IntelliJ project to view documentation of the available command line options in the console window.

Remember that the contract manager deploys transactions to the blockchain. For the transactions to confirm you need to wait for the next block.

7.1 Examples

Deploy a new version of the ForgingReward contract:

contractManager.bat -u -n ForgingReward

Response:

...

Contract class com.jelurida.ardor.contracts.ForgingReward uploaded 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748 ...

Contract reference registered ForgingReward={"rewardArdor":"true","interval":5,"rewardNxt":"true","rewardChain":"IGNIS","rewardAmountNQT":150000000} for contract chain: IGNIS, full hash: 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748


List deployed contracts for a specific account:

contractManager.bat -l -a ARDOR-XK4R-7VJU-6EQG-7R335

Response:

{"contract":{"chain":2,"transactionFullHash":"ff374385044519900147c145beeaa07520f0a9850b95d84b835c88ff1ce81015"},"name":"DistributedRandomNumberGenerator","id":"10806939862175194746","params":"{}"}

{"contract":{"chain":2,"transactionFullHash":"5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748"},"name":"ForgingReward","id":"10360831005888949167","params":"{\"rewardArdor\":\"true\",\"interval\":5,\"rewardNxt\":\"true\",\"rewardChain\":\"IGNIS\",\"rewardAmountNQT\":150000000}"}

{"contract":{"chain":2,"transactionFullHash":"8292a5e3a9bd209a85b3a865be8175d1c762393dac33e84a3113e19a7efc0e39"},"name":"NewAccountFaucet","id":"4615352619232966745","params":"{\"thresholdAmountNQT\":72000000000,\"chain\":2,\"thresholdBlocks\":1440,\"faucetAmountNQT\":500000000}"}


Delete contract reference for a specific account (the contract itself is not deleted)

contractManager.bat -d -a ARDOR-XK4R-7VJU-6EQG-7R335 -n ForgingReward

Response:

Contract reference 10360831005888949167 deleted for account ARDOR-XK4R-7VJU-6EQG-7R335 contract name ForgingReward


Add or update contract reference

contractManager.bat -r -h 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748 -n ForgingReward

(the hash parameter -h is the full hash of the cloud data transaction storing the contract version you would like to reference)

Response:

Contract reference registered ForgingReward={"rewardArdor":"true","interval":5,"rewardNxt":"true","rewardChain":"IGNIS","rewardAmountNQT":150000000} for contract chain: IGNIS, full hash: 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748