API: Contracts¶
注解
Before reading this page, you should be familiar with the key concepts of Contracts.
注解
当你阅读这里的时候,你应该已经熟悉了核心概念 Contracts。
Contract¶
Contracts are classes that implement the Contract
interface. The Contract
interface is defined as follows:
Contracts 都是实现了 Contract
接口的类。Contract
接口定义如下:
/**
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
* every [net.corda.core.transactions.LedgerTransaction] they see on the network, for every input and output state. All
* contracts must accept the transaction for it to be accepted: failure of any aborts the entire thing. The time is taken
* from a trusted time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
*
* TODO: Contract serialization is likely to change, so the annotation is likely temporary.
*/
@KeepForDJVM
@CordaSerializable
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
has a single method, verify
, which takes a LedgerTransaction
as input and returns
nothing. This function is used to check whether a transaction proposal is valid, as follows:
- We gather together the contracts of each of the transaction’s input and output states
- We call each contract’s
verify
function, passing in the transaction as an input - The proposal is only valid if none of the
verify
calls throw an exception
Contract
只有一个 verify
方法,它会有一个 LedgerTransaction
作为 input 参数并且不会返回任何内容。这个方法被用来检验一个交易的提议是否有效,包括下边的验证:
- 我们会搜集这个交易的 input 和 output states 的 contract code
- 我们会调用每个 contract code 的
verify
方法,将 transaction 作为 input 传进去 - 这个更新账本的提议仅仅在所有的 verify 方法都没有返回 exception 的情况下才算是有效的
verify
is executed in a sandbox:
- It does not have access to the enclosing scope
- The libraries available to it are whitelisted to disallow: * Network access * I/O such as disk or database access * Sources of randomness such as the current time or random number generators
verify
是在一个 sandbox 中执行的:
- 它没有权限访问内部的内容
- 针对于它可用的类库被放入白名单来不允许: * 网络访问 * 硬盘或数据库访问的 I/O * 随机的资源比如当前的时间或者随机数生成器
This means that verify
only has access to the properties defined on LedgerTransaction
when deciding whether a
transaction is valid.
这意味着 verify
仅仅能够在决定一个交易是否有效的时候才能够访问 LedgerTransaction
中定义的属性。
Here are the two simplest verify
functions:
最简单的两个 verify 方法:
- A
verify
that accepts all possible transactions: - 一个
verify
接受 所有可能的 transactions:
override fun verify(tx: LedgerTransaction) {
// Always accepts!
}
@Override
public void verify(LedgerTransaction tx) {
// Always accepts!
}
- A
verify
that rejects all possible transactions: - 一个
verify
拒绝 所有的 transactions:
override fun verify(tx: LedgerTransaction) {
throw IllegalArgumentException("Always rejects!")
}
@Override
public void verify(LedgerTransaction tx) {
throw new IllegalArgumentException("Always rejects!");
}
LedgerTransaction¶
The LedgerTransaction
object passed into verify
has the following properties:
被传入 verify 方法中的 LedgerTransaction
对象具有以下属性:
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
override val inputs: List<StateAndRef<ContractState>>,
/** The outputs created by the transaction. */
override val outputs: List<TransactionState<ContractState>>,
/** Arbitrary data passed to the program of each input state. */
val commands: List<CommandWithParties<CommandData>>,
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
val attachments: List<Attachment>,
/** The hash of the original serialised WireTransaction. */
override val id: SecureHash,
/** The notary that the tx uses, this must be the same as the notary of all the inputs, or null if there are no inputs. */
override val notary: Party?,
/** The time window within which the tx is valid, will be checked against notary pool member clocks. */
val timeWindow: TimeWindow?,
/** Random data used to make the transaction hash unpredictable even if the contents can be predicted; needed to avoid some obscure attacks. */
val privacySalt: PrivacySalt,
/**
* Network parameters that were in force when the transaction was constructed. This is nullable only for backwards
* compatibility for serialized transactions. In reality this field will always be set when on the normal codepaths.
*/
override val networkParameters: NetworkParameters?,
/** Referenced states, which are like inputs but won't be consumed. */
override val references: List<StateAndRef<ContractState>>
Where:
inputs
are the transaction’s inputs asList<StateAndRef<ContractState>>
outputs
are the transaction’s outputs asList<TransactionState<ContractState>>
commands
are the transaction’s commands and associated signers, asList<CommandWithParties<CommandData>>
attachments
are the transaction’s attachments asList<Attachment>
notary
is the transaction’s notary. This must match the notary of all the inputstimeWindow
defines the window during which the transaction can be notarised
其中:
inputs
是类型为List<StateAndRef<ContractState>>
的 transaction 的 inputsoutputs
是类型为List<TransactionState<ContractState>>
的 transaction 的 outputscommands
是类型为List<CommandWithParties<CommandData>>
的 transaction 的 commands 和相关的签名者attachments
是类型为List<Attachment>
的 transaction 的 attachmentsnotary
是 transaction 的 notary。这个必须要同所有的 inputs 拥有相同的 notarytimeWindow
定义了一笔交易在什么样的时间窗内才会被公正
LedgerTransaction
exposes a large number of utility methods to access the transaction’s contents:
inputStates
extracts the inputContractState
objects from the list ofStateAndRef
getInput
/getOutput
/getCommand
/getAttachment
extracts a component by indexgetAttachment
extracts an attachment by IDinputsOfType
/inRefsOfType
/outputsOfType
/outRefsOfType
/commandsOfType
extracts components based on their generic typefilterInputs
/filterInRefs
/filterOutputs
/filterOutRefs
/filterCommands
extracts components based on a predicatefindInput
/findInRef
/findOutput
/findOutRef
/findCommand
extracts the single component that matches a predicate, or throws an exception if there are multiple matches
LedgerTransaction
暴漏了很多 utility 方法来访问交易的内容:
inputStates
从StateAndRef
列表中获得 inputContractState
对象getInput
/getOutput
/getCommand
/getAttachment
通过索引(index)来获得某个组件getAttachment
通过 ID 获得一个附件inputsOfType
/inRefsOfType
/outputsOfType
/outRefsOfType
/commandsOfType
基于他们的通用类型获得相关组件filterInputs
/filterInRefs
/filterOutputs
/filterOutRefs
/filterCommands
基于一个前提条件获得相关组件findInput
/findInRef
/findOutput
/findOutRef
/findCommand
获得满足一定条件的单一组件,或者当有多个满足条件的组件的时候抛出异常
requireThat¶
verify
can be written to manually throw an exception for each constraint:
verify
能够针对每一个约束手动地抛出异常:
override fun verify(tx: LedgerTransaction) {
if (tx.inputs.size > 0)
throw IllegalArgumentException("No inputs should be consumed when issuing an X.")
if (tx.outputs.size != 1)
throw IllegalArgumentException("Only one output state should be created.")
}
public void verify(LedgerTransaction tx) {
if (tx.getInputs().size() > 0)
throw new IllegalArgumentException("No inputs should be consumed when issuing an X.");
if (tx.getOutputs().size() != 1)
throw new IllegalArgumentException("Only one output state should be created.");
}
However, this is verbose. To impose a series of constraints, we can use requireThat
instead:
但是这个定义有些繁琐。我们可以使用 requireThat
来定义一系列的约束:
requireThat {
"No inputs should be consumed when issuing an X." using (tx.inputs.isEmpty())
"Only one output state should be created." using (tx.outputs.size == 1)
val out = tx.outputs.single() as XState
"The sender and the recipient cannot be the same entity." using (out.sender != out.recipient)
"All of the participants must be signers." using (command.signers.containsAll(out.participants))
"The X's value must be non-negative." using (out.x.value > 0)
}
requireThat(require -> {
require.using("No inputs should be consumed when issuing an X.", tx.getInputs().isEmpty());
require.using("Only one output state should be created.", tx.getOutputs().size() == 1);
final XState out = (XState) tx.getOutputs().get(0);
require.using("The sender and the recipient cannot be the same entity.", out.getSender() != out.getRecipient());
require.using("All of the participants must be signers.", command.getSigners().containsAll(out.getParticipants()));
require.using("The X's value must be non-negative.", out.getX().getValue() > 0);
return null;
});
For each <String
, Boolean
> pair within requireThat
, if the boolean condition is false, an
IllegalArgumentException
is thrown with the corresponding string as the exception message. In turn, this
exception will cause the transaction to be rejected.
对于 requireThat
中的每一个 <String
, Boolean
> 对来说,如果 boolean 条件返回的是 false,一个 IllegalArgumentException
会被抛出,包含对应的错误信息。所以这个错误会造成 transaction 被拒绝。
Commands¶
LedgerTransaction
contains the commands as a list of CommandWithParties
instances. CommandWithParties
pairs
a CommandData
with a list of required signers for the transaction:
LedgerTransaction
包含了作为 CommandWithParties
实例列表的 commands。CommandWithParties
将一个 CommandData
和一个所需的签名者列表匹配起来:
/** A [Command] where the signing parties have been looked up if they have a well known/recognised institutional key. */
@KeepForDJVM
@CordaSerializable
data class CommandWithParties<out T : CommandData>(
val signers: List<PublicKey>,
/** If any public keys were recognised, the looked up institutions are available here */
@Deprecated("Should not be used in contract verification code as it is non-deterministic, will be disabled for some future target platform version onwards and will take effect only for CorDapps targeting those versions.")
val signingParties: List<Party>,
val value: T
)
Where:
signers
is the list of each signer’sPublicKey
signingParties
is the list of the signer’s identities, if knownvalue
is the object being signed (a command, in this case)
其中:
signers
是每个签名者的PublicKey
的一个列表signingParties
签名者 identities 的列表,如果知道的话value
是被签名的对象(在这里指的是这个 command)
使用 commands 来处理 verify 分支¶
Generally, we will want to impose different constraints on a transaction based on its commands. For example, we will want to impose different constraints on a cash issuance transaction to on a cash transfer transaction.
通常来说,我们希望基于交易的 commands 来定义不同的约束条件。比如我们想要为一个现金发行的 transaction 和 一个现金交换的 transaction 定义不同的合约。
We can achieve this by extracting the command and using standard branching logic within verify
. Here, we extract
the single command of type XContract.Commands
from the transaction, and branch verify
accordingly:
我们可以通过提取这个 command 并在 verify
里使用标准的分支逻辑来实现这个功能。这里我们提取了交易中的类型为 XContract.Commands
的单独的 command,并且相应地对 verify
进行了分支逻辑判断:
class XContract : Contract {
interface Commands : CommandData {
class Issue : TypeOnlyCommandData(), Commands
class Transfer : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
val command = tx.findCommand<Commands> { true }
when (command.value) {
is Commands.Issue -> {
// Issuance verification logic.
}
is Commands.Transfer -> {
// Transfer verification logic.
}
}
}
}
public class XContract implements Contract {
public interface Commands extends CommandData {
class Issue extends TypeOnlyCommandData implements Commands {}
class Transfer extends TypeOnlyCommandData implements Commands {}
}
@Override
public void verify(LedgerTransaction tx) {
final Commands command = tx.findCommand(Commands.class, cmd -> true).getValue();
if (command instanceof Commands.Issue) {
// Issuance verification logic.
} else if (command instanceof Commands.Transfer) {
// Transfer verification logic.
}
}
}