环境准备

  • JDK(8u131以上)

corda训练营

代码地址:

https://github.com/corda/bootcamp-cordapp

个人gitee项目:https://gitee.com/zheshiyigegexingwangzhan/bootcamp-cordapp.git

添加国内镜像

当前项目修改

下载代码之后为了更快的下载依赖,添加国内的镜像:

  1. maven { url 'http://maven.aliyun.com/nexus/content/groups/public/'}

直接修改全局的gradle配置

~/.gradle目录下新建init.gradle文件,写入以下内容:

  1. allprojects{
  2. repositories {
  3. def ALIYUN_REPOSITORY_URL = 'http://maven.aliyun.com/nexus/content/groups/public'
  4. def ALIYUN_JCENTER_URL = 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
  5. all { ArtifactRepository repo ->
  6. if(repo instanceof MavenArtifactRepository){
  7. def url = repo.url.toString()
  8. if (url.startsWith('https://repo1.maven.org/maven2')) {
  9. project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_REPOSITORY_URL."
  10. remove repo
  11. }
  12. if (url.startsWith('https://jcenter.bintray.com/')) {
  13. project.logger.lifecycle "Repository ${repo.url} replaced by $ALIYUN_JCENTER_URL."
  14. remove repo
  15. }
  16. }
  17. }
  18. maven {
  19. url ALIYUN_REPOSITORY_URL
  20. url ALIYUN_JCENTER_URL
  21. }
  22. }
  23. }

测试代码

运行ProjectImportedOKTest单测,如果通过说明环境没有问题

我的运行结果如下:

  1. > Configure project :
  2. Repository https://jcenter.bintray.com/ replaced by http://maven.aliyun.com/nexus/content/repositories/jcenter.
  3. > Task :compileJava
  4. > Task :processResources NO-SOURCE
  5. > Task :classes
  6. > Task :compileTestJava
  7. > Task :processTestResources NO-SOURCE
  8. > Task :testClasses
  9. > Task :test

代码开发

State开发

一个state需要实现ContractState,ContractState中有一个方法getParticipants(),返回的是List<AbstractParty>,表示在这个state发生了交易时需要通知谁,让谁知道。

  1. package bootcamp;
  2. import com.google.common.collect.ImmutableList;
  3. import net.corda.core.contracts.ContractState;
  4. import net.corda.core.identity.AbstractParty;
  5. import net.corda.core.identity.Party;
  6. import org.jetbrains.annotations.NotNull;
  7. import java.util.List;
  8. public class IouState implements ContractState {
  9. /**
  10. * 发行人
  11. */
  12. private Party issuer;
  13. /**
  14. * 拥有者
  15. */
  16. private Party owner;
  17. /**
  18. * 金额
  19. */
  20. private int amount;
  21. public IouState(Party issuer, Party owner, int amount) {
  22. this.issuer = issuer;
  23. this.owner = owner;
  24. this.amount = amount;
  25. }
  26. @NotNull
  27. @Override
  28. public List<AbstractParty> getParticipants() {
  29. return ImmutableList.of(issuer, owner);
  30. }
  31. public Party getIssuer() {
  32. return issuer;
  33. }
  34. public Party getOwner() {
  35. return owner;
  36. }
  37. public int getAmount() {
  38. return amount;
  39. }
  40. }

Contract开发

一个contract简单的理解就是一些校验的规则,需要实现Contract类,Contract类如下,只有一个verify方法,验证LedgerTransaction是否正确,如果不正确就抛IllegalArgumentException异常。

  1. interface Contract {
  2. /**
  3. * Takes an object that represents a state transition, and ensures the inputs/outputs/commands make sense.
  4. * Must throw an exception if there's a problem that should prevent state transition. Takes a single object
  5. * rather than an argument so that additional data can be added without breaking binary compatibility with
  6. * existing contract code.
  7. */
  8. @Throws(IllegalArgumentException::class)
  9. fun verify(tx: LedgerTransaction)
  10. }

在开发一个contract时,Corda提议的三个验证的类型:

  • 输入与输出个数的校验(Shape Constraint,No. input states, No. output states, command)
  • 输入与输出的内容的校验(Context Constraint),业务校验
  • 需要的签名的校验(Required Singer Constraint)

区块链实战-如何开发一个CorDapp - 图1

按照上图的规则IouContract的实现如下:

  1. package bootcamp;
  2. import net.corda.core.contracts.Command;
  3. import net.corda.core.contracts.CommandData;
  4. import net.corda.core.contracts.Contract;
  5. import net.corda.core.contracts.ContractState;
  6. import net.corda.core.transactions.LedgerTransaction;
  7. import org.apache.commons.collections4.CollectionUtils;
  8. public class IouContract implements Contract {
  9. public static String ID = "bootcamp.IouContract";
  10. @Override
  11. public void verify(LedgerTransaction tx) {
  12. // 1、Shape Constraint,No. input states, No. output states, command
  13. if (tx.getCommands().size() != 1) {
  14. throw new IllegalArgumentException("command size must be one");
  15. }
  16. Command<CommandData> command = tx.getCommand(0);
  17. if (!(command.getValue() instanceof Commands.Issue)) {
  18. throw new IllegalArgumentException("command must be Issue");
  19. }
  20. if (CollectionUtils.isNotEmpty(tx.getInputs())) {
  21. throw new IllegalArgumentException("Issue must be not inputs");
  22. }
  23. if (tx.getOutputs().size() != 1) {
  24. throw new IllegalArgumentException("Issue outputs must be one");
  25. }
  26. ContractState output = tx.getOutput(0);
  27. // 2、Context Constraint
  28. if (!(output instanceof IouState)) {
  29. throw new IllegalArgumentException("state must be IouState");
  30. }
  31. IouState iouState = (IouState) output;
  32. if (iouState.getAmount() <= 0) {
  33. throw new IllegalArgumentException("issue amount must big than zero");
  34. }
  35. // 3、Required Singer Constraint
  36. if (!command.getSigners().contains(iouState.getIssuer().getOwningKey())) {
  37. throw new IllegalArgumentException("issue business must be sing by issuer");
  38. }
  39. }
  40. public interface Commands extends CommandData {
  41. class Issue implements Commands {
  42. }
  43. }
  44. }

Flow开发

flow有两种

  • 可以在本地主动启动的flow
  • 只能通过其他的flow启动的flow

发起一个交易的flow都是可以在本地主动启动的flow,有以下特点

  • 需要添加注解@InitiatingFlow来表示他是一个可以初始化的flow
  • 需要添加注@StartableByRPC或者@StartableByService来说明启动的方式
  • flow需要继承自FlowLogic,业务逻辑在call方法中实现
  • call方法需要添加@Suspendable注解
  • 指定notary,校验是否双花
  • 创建交易,交易中必须包含command,如果有output必须指定contract来进行验证;可以没有input
  • 然后就是通用的流程,验证交易、收集签名、交易入库
  1. package bootcamp;
  2. import co.paralleluniverse.fibers.Suspendable;
  3. import com.google.common.collect.ImmutableList;
  4. import net.corda.core.contracts.StateAndRef;
  5. import net.corda.core.flows.*;
  6. import net.corda.core.identity.Party;
  7. import net.corda.core.transactions.SignedTransaction;
  8. import net.corda.core.transactions.TransactionBuilder;
  9. import net.corda.core.utilities.ProgressTracker;
  10. import static java.util.Collections.singletonList;
  11. /**
  12. *
  13. * @author nicai
  14. */
  15. @InitiatingFlow
  16. @StartableByRPC
  17. public class IouIssueFlowInitiator extends FlowLogic<SignedTransaction> {
  18. private final Party owner;
  19. private final int amount;
  20. public IouIssueFlowInitiator(Party owner, int amount) {
  21. this.owner = owner;
  22. this.amount = amount;
  23. }
  24. private final ProgressTracker progressTracker = new ProgressTracker();
  25. @Override
  26. public ProgressTracker getProgressTracker() {
  27. return progressTracker;
  28. }
  29. @Suspendable
  30. @Override
  31. public SignedTransaction call() throws FlowException {
  32. // We choose our transaction's notary (the notary prevents double-spends).
  33. Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
  34. // We get a reference to our own identity.
  35. Party issuer = getOurIdentity();
  36. // We create our new IouState.
  37. IouState iouState = new IouState(issuer, owner, amount);
  38. // We build our transaction.
  39. TransactionBuilder transactionBuilder = new TransactionBuilder(notary)
  40. // .addInputState()
  41. .addOutputState(iouState, IouContract.ID)
  42. .addCommand(new IouContract.Commands.Issue(), ImmutableList.of(issuer.getOwningKey(), owner.getOwningKey()));
  43. // We check our transaction is valid based on its contracts.
  44. transactionBuilder.verify(getServiceHub());
  45. FlowSession session = initiateFlow(owner);
  46. // We sign the transaction with our private key, making it immutable.
  47. SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(transactionBuilder);
  48. // The counterparty signs the transaction
  49. SignedTransaction fullySignedTransaction = subFlow(new CollectSignaturesFlow(signedTransaction, singletonList(session)));
  50. // We get the transaction notarised and recorded automatically by the platform.
  51. return subFlow(new FinalityFlow(fullySignedTransaction, singletonList(session)));
  52. }
  53. }

被动启动的flow有以下特点:

  • 需要注解@InitiatedBy(IouIssueFlowInitiator.class)指定谁能启动这个flow
  • flow需要继承自FlowLogic,业务逻辑在call方法中实现
  • call方法需要添加@Suspendable注解
  • 需要有实例变量FlowSession,保存调用者的FlowSession
  • call方法需要验证交易,然后执行接收交易的标准流程ReceiveFinalityFlow
  1. package bootcamp;
  2. import co.paralleluniverse.fibers.Suspendable;
  3. import net.corda.core.flows.*;
  4. import net.corda.core.transactions.SignedTransaction;
  5. @InitiatedBy(IouIssueFlowInitiator.class)
  6. public class IouIssueFlowResponder extends FlowLogic<Void> {
  7. private final FlowSession otherSide;
  8. public IouIssueFlowResponder(FlowSession otherSide) {
  9. this.otherSide = otherSide;
  10. }
  11. @Override
  12. @Suspendable
  13. public Void call() throws FlowException {
  14. SignedTransaction signedTransaction = subFlow(new SignTransactionFlow(otherSide) {
  15. @Suspendable
  16. @Override
  17. protected void checkTransaction(SignedTransaction stx) throws FlowException {
  18. // Implement responder flow transaction checks here
  19. }
  20. });
  21. subFlow(new ReceiveFinalityFlow(otherSide, signedTransaction.getId()));
  22. return null;
  23. }
  24. }

运行

打包

  1. ./gradlew deployNodes

运行所有的节点

  1. sudo ./build/nodes/runnodes

启动一个流程

  1. flow start IouIssueFlow owner: PartyB, amount: 99

我本地日志如下:

  1. Starting
  2. Requesting signature by notary service
  3. Requesting signature by Notary service
  4. Validating response from Notary service
  5. Broadcasting transaction to participants
  6. ➡️ Done
  7. Flow completed with result: SignedTransaction(id=14D268667D208D26BF92ADC1F58003DFC9EAF7E036ACB2C2CABC153E627500C0)

查询生成的数据

  1. run vaultQuery contractStateType: bootcamp.IouState

我本地的结果如下

  1. states:
  2. - state:
  3. data: !<bootcamp.IouState>
  4. issuer: "O=PartyA, L=London, C=GB"
  5. owner: "O=PartyB, L=New York, C=US"
  6. amount: 99
  7. contract: "bootcamp.IouContract"
  8. notary: "O=Notary, L=London, C=GB"
  9. encumbrance: null
  10. constraint: !<net.corda.core.contracts.SignatureAttachmentConstraint>
  11. key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  12. ref:
  13. txhash: "14D268667D208D26BF92ADC1F58003DFC9EAF7E036ACB2C2CABC153E627500C0"
  14. index: 0
  15. statesMetadata:
  16. - ref:
  17. txhash: "14D268667D208D26BF92ADC1F58003DFC9EAF7E036ACB2C2CABC153E627500C0"
  18. index: 0
  19. contractStateClassName: "bootcamp.IouState"
  20. recordedTime: "2020-12-03T09:49:48.373Z"
  21. consumedTime: null
  22. status: "UNCONSUMED"
  23. notary: "O=Notary, L=London, C=GB"
  24. lockId: null
  25. lockUpdateTime: null
  26. relevancyStatus: "RELEVANT"
  27. constraintInfo:
  28. constraint:
  29. key: "aSq9DsNNvGhYxYyqA9wd2eduEAZ5AXWgJTbTEw3G5d2maAq8vtLE4kZHgCs5jcB1N31cx1hpsLeqG2ngSysVHqcXhbNts6SkRWDaV7xNcr6MtcbufGUchxredBb6"
  30. totalStatesAvailable: -1
  31. stateTypes: "UNCONSUMED"
  32. otherResults: []

节点可视化工具

参考网站:https://docs.corda.net/docs/corda-os/4.6/node-explorer.html

可以下载node-explorer来查看节点信息。

第一次打开界面

区块链实战-如何开发一个CorDapp - 图2

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

区块链实战-如何开发一个CorDapp - 图3

区块链实战-如何开发一个CorDapp - 图4

使用spring开发corda:

https://manosbatsis.github.io/corbeans/