环境准备
- JDK(8u131以上)
corda训练营
代码地址:
https://github.com/corda/bootcamp-cordapp
个人gitee项目:https://gitee.com/zheshiyigegexingwangzhan/bootcamp-cordapp.git
添加国内镜像
当前项目修改
下载代码之后为了更快的下载依赖,添加国内的镜像:
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/'}
直接修改全局的gradle配置
在~/.gradle目录下新建init.gradle文件,写入以下内容:
allprojects{repositories {def ALIYUN_REPOSITORY_URL = 'http://maven.aliyun.com/nexus/content/groups/public'def ALIYUN_JCENTER_URL = 'http://maven.aliyun.com/nexus/content/repositories/jcenter'all { ArtifactRepository repo ->if(repo instanceof MavenArtifactRepository){def url = repo.url.toString()if (url.startsWith('https://repo1.maven.org/maven2')) {project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_REPOSITORY_URL."remove repo}if (url.startsWith('https://jcenter.bintray.com/')) {project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_JCENTER_URL."remove repo}}}maven {url ALIYUN_REPOSITORY_URLurl ALIYUN_JCENTER_URL}}}
测试代码
运行ProjectImportedOKTest单测,如果通过说明环境没有问题
我的运行结果如下:
> Configure project :Repository https://jcenter.bintray.com/ replaced by http://maven.aliyun.com/nexus/content/repositories/jcenter.> Task :compileJava> Task :processResources NO-SOURCE> Task :classes> Task :compileTestJava> Task :processTestResources NO-SOURCE> Task :testClasses> Task :test
代码开发
State开发
一个state需要实现ContractState,ContractState中有一个方法getParticipants(),返回的是List<AbstractParty>,表示在这个state发生了交易时需要通知谁,让谁知道。
package bootcamp;import com.google.common.collect.ImmutableList;import net.corda.core.contracts.ContractState;import net.corda.core.identity.AbstractParty;import net.corda.core.identity.Party;import org.jetbrains.annotations.NotNull;import java.util.List;public class IouState implements ContractState {/*** 发行人*/private Party issuer;/*** 拥有者*/private Party owner;/*** 金额*/private int amount;public IouState(Party issuer, Party owner, int amount) {this.issuer = issuer;this.owner = owner;this.amount = amount;}@NotNull@Overridepublic List<AbstractParty> getParticipants() {return ImmutableList.of(issuer, owner);}public Party getIssuer() {return issuer;}public Party getOwner() {return owner;}public int getAmount() {return amount;}}
Contract开发
一个contract简单的理解就是一些校验的规则,需要实现Contract类,Contract类如下,只有一个verify方法,验证LedgerTransaction是否正确,如果不正确就抛IllegalArgumentException异常。
interface Contract {/*** Takes an object that represents a state transition, and ensures the inputs/outputs/commands make sense.* Must throw an exception if there's a problem that should prevent state transition. Takes a single object* rather than an argument so that additional data can be added without breaking binary compatibility with* existing contract code.*/@Throws(IllegalArgumentException::class)fun verify(tx: LedgerTransaction)}
在开发一个contract时,Corda提议的三个验证的类型:
- 输入与输出个数的校验(Shape Constraint,No. input states, No. output states, command)
- 输入与输出的内容的校验(Context Constraint),业务校验
- 需要的签名的校验(Required Singer Constraint)

按照上图的规则IouContract的实现如下:
package bootcamp;import net.corda.core.contracts.Command;import net.corda.core.contracts.CommandData;import net.corda.core.contracts.Contract;import net.corda.core.contracts.ContractState;import net.corda.core.transactions.LedgerTransaction;import org.apache.commons.collections4.CollectionUtils;public class IouContract implements Contract {public static String ID = "bootcamp.IouContract";@Overridepublic void verify(LedgerTransaction tx) {// 1、Shape Constraint,No. input states, No. output states, commandif (tx.getCommands().size() != 1) {throw new IllegalArgumentException("command size must be one");}Command<CommandData> command = tx.getCommand(0);if (!(command.getValue() instanceof Commands.Issue)) {throw new IllegalArgumentException("command must be Issue");}if (CollectionUtils.isNotEmpty(tx.getInputs())) {throw new IllegalArgumentException("Issue must be not inputs");}if (tx.getOutputs().size() != 1) {throw new IllegalArgumentException("Issue outputs must be one");}ContractState output = tx.getOutput(0);// 2、Context Constraintif (!(output instanceof IouState)) {throw new IllegalArgumentException("state must be IouState");}IouState iouState = (IouState) output;if (iouState.getAmount() <= 0) {throw new IllegalArgumentException("issue amount must big than zero");}// 3、Required Singer Constraintif (!command.getSigners().contains(iouState.getIssuer().getOwningKey())) {throw new IllegalArgumentException("issue business must be sing by issuer");}}public interface Commands extends CommandData {class Issue implements Commands {}}}
Flow开发
flow有两种
- 可以在本地主动启动的flow
- 只能通过其他的flow启动的flow
发起一个交易的flow都是可以在本地主动启动的flow,有以下特点
- 需要添加注解
@InitiatingFlow来表示他是一个可以初始化的flow - 需要添加注
@StartableByRPC或者@StartableByService来说明启动的方式 - flow需要继承自
FlowLogic,业务逻辑在call方法中实现 - call方法需要添加
@Suspendable注解 - 指定notary,校验是否双花
- 创建交易,交易中必须包含command,如果有output必须指定contract来进行验证;可以没有input
- 然后就是通用的流程,验证交易、收集签名、交易入库
package bootcamp;import co.paralleluniverse.fibers.Suspendable;import com.google.common.collect.ImmutableList;import net.corda.core.contracts.StateAndRef;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 static java.util.Collections.singletonList;/**** @author nicai*/@InitiatingFlow@StartableByRPCpublic class IouIssueFlowInitiator extends FlowLogic<SignedTransaction> {private final Party owner;private final int amount;public IouIssueFlowInitiator(Party owner, int amount) {this.owner = owner;this.amount = amount;}private final ProgressTracker progressTracker = new ProgressTracker();@Overridepublic ProgressTracker getProgressTracker() {return progressTracker;}@Suspendable@Overridepublic SignedTransaction call() throws FlowException {// We choose our transaction's notary (the notary prevents double-spends).Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);// We get a reference to our own identity.Party issuer = getOurIdentity();// We create our new IouState.IouState iouState = new IouState(issuer, owner, amount);// We build our transaction.TransactionBuilder transactionBuilder = new TransactionBuilder(notary)// .addInputState().addOutputState(iouState, IouContract.ID).addCommand(new IouContract.Commands.Issue(), ImmutableList.of(issuer.getOwningKey(), owner.getOwningKey()));// We check our transaction is valid based on its contracts.transactionBuilder.verify(getServiceHub());FlowSession session = initiateFlow(owner);// We sign the transaction with our private key, making it immutable.SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder);// The counterparty signs the transactionSignedTransaction fullySignedTransaction = subFlow(new CollectSignaturesFlow(signedTransaction, singletonList(session)));// We get the transaction notarised and recorded automatically by the platform.return subFlow(new FinalityFlow(fullySignedTransaction, singletonList(session)));}}
被动启动的flow有以下特点:
- 需要注解
@InitiatedBy(IouIssueFlowInitiator.class)指定谁能启动这个flow - flow需要继承自
FlowLogic,业务逻辑在call方法中实现 - call方法需要添加
@Suspendable注解 - 需要有实例变量FlowSession,保存调用者的FlowSession
- call方法需要验证交易,然后执行接收交易的标准流程
ReceiveFinalityFlow
package bootcamp;import co.paralleluniverse.fibers.Suspendable;import net.corda.core.flows.*;import net.corda.core.transactions.SignedTransaction;@InitiatedBy(IouIssueFlowInitiator.class)public class IouIssueFlowResponder extends FlowLogic<Void> {private final FlowSession otherSide;public IouIssueFlowResponder(FlowSession otherSide) {this.otherSide = otherSide;}@Override@Suspendablepublic Void call() throws FlowException {SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(otherSide) {@Suspendable@Overrideprotected void checkTransaction(SignedTransaction stx) throws FlowException {// Implement responder flow transaction checks here}});subFlow(new ReceiveFinalityFlow(otherSide, signedTransaction.getId()));return null;}}
运行
打包
./gradlew deployNodes
运行所有的节点
sudo ./build/nodes/runnodes
启动一个流程
flow start IouIssueFlow owner: PartyB, amount: 99
我本地日志如下:
✅ StartingRequesting signature by notary serviceRequesting signature by Notary serviceValidating response from Notary service✅ Broadcasting transaction to participants➡️ DoneFlow completed with result: SignedTransaction(id=14D268667D208D26BF92ADC1F58003DFC9EAF7E036ACB2C2CABC153E627500C0)
查询生成的数据
run vaultQuery contractStateType: bootcamp.IouState
我本地的结果如下
states:- state:data: !<bootcamp.IouState>issuer: "O=PartyA, L=London, C=GB"owner: "O=PartyB, L=New York, C=US"amount: 99contract: "bootcamp.IouContract"notary: "O=Notary, L=London, C=GB"encumbrance: nullconstraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"ref:txhash: "14D268667D208D26BF92ADC1F58003DFC9EAF7E036ACB2C2CABC153E627500C0"index: 0statesMetadata:- ref:txhash: "14D268667D208D26BF92ADC1F58003DFC9EAF7E036ACB2C2CABC153E627500C0"index: 0contractStateClassName: "bootcamp.IouState"recordedTime: "2020-12-03T09:49:48.373Z"consumedTime: nullstatus: "UNCONSUMED"notary: "O=Notary, L=London, C=GB"lockId: nulllockUpdateTime: nullrelevancyStatus: "RELEVANT"constraintInfo:constraint:key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"totalStatesAvailable: -1stateTypes: "UNCONSUMED"otherResults: []
节点可视化工具
参考网站:https://docs.corda.net/docs/corda-os/4.6/node-explorer.html
可以下载node-explorer来查看节点信息。
第一次打开界面

- Node Hostname:localhost
- Node Port:RPC connection address可以在启动的窗口查看,或者配置文件查看
- RPC Username:在配置文件
- RPC Password:在配置文件查看


使用spring开发corda:
