The Crosschain Interoperability Specification is a set of documents that define the implementation requirements for Enterprise Ethereum clients that do cross-blockchain communications. It defines the architectures, protocols and interfaces that facilitate discovery and enable scaleable crosschain communications.
This document describes the Crosschain Function Call interfaces and protocols that can be used to provide function calls across blockchains for Crosschain Applications.
This is a working document. It is inappropriate to cite this document other than as a work in progress.
Please send any comments to the editors of this document.
Github Issues are preferred for discussion of this document.
This document is available under the terms of the Apache License 2.0.
When referencing this document, the following citation format should be used:
[eeaciw-functioncall-v1.0] EEA CIW - Technical Specification Function Call Interface Draft Version 1.0. Edited by Weijia Zhang, Peter Robinson and Aiman Baharna. 28 February 2022. EEA CIW. https://entethalliance.github.io/crosschain-interoperability/draft_crosschain_techspec_function.html . Latest stage: https://entethalliance.github.io/crosschain-interoperability/draft_crosschain_techspec_function.html .
The Crosschain Protocol Stack defines a way for enterprises to create interoperable components for crosschain communications. As shown in the diagram below, Crosschain Applications use Crosschain Function calls to allow business logic to be executed across blockchains. Components in the Crosschain Function Call layer rely on components in the Crosschain Messaging layer to deliver information from one blockchain to another such that the information can be trusted. This specification defines the interface that components in the Crosschain Application layer can use to call functions across blockchains, authenticate the identity of calling parties, and manage contract locking.
Crosschain function call protocols can be atomic or non-atomic. Non-atomic protocols do not ensure consistency across blockchains. That is, a segment of the overall crosschain transaction may occur on a source blockchain, with associated updates, but the transaction on the destination blockchain may fail, and hence the updates would not be applied on the destination blockchain. A crosschain transaction segment could fail for any of the reasons described below. Non-atomic protocols must provide a mechanism to resolve failures such that consistency is restored.
Additionally, failures could occur on the source blockchain.
Atomic protocols ensure consistency across blockchains. That is, crosschain transaction segments either all succeed and updates are committed, or one or more segments fail and all updates are discarded. The precise locking model is defined by the application layer.
Section 2 defines Solidity interfaces that must be implemented by complying protocol implementations. Section 3 defines the Simple Function Call protocol, a non-atomic protocol. Section 4 defines the General Purpose Atomic Crosschain Transaction (GPACT) protocol.
The specification is currently targetted at Ethereum Virtual Machine (EVM) compatible blockchains. It defines interfaces using the Solidity programming language. It is expected that the interfaces should be able to be written in other programming languages to offer the same functionality on other blockchains.
This section defines Solidity interfaces between the Crosschain Application Layer and the Crosschain Function Call Layer for both Non-Atomic and Atomic Function Call protocols.
The sub-sections below describe Solidity interfaces and helper functions between the Crosschain Application Layer and the Crosschain Function Call Layer for Non-Atomic Function Call implementations.
The Crosschain Function Call Interface allows applications to call functions on other blockchains.
R1
: Non-Atomic Function Call implementations MUST support the
CrosschainFunctionCallInterface
interface.
pragma solidity >=0.8;
interface CrosschainFunctionCallInterface {
function crossBlockchainCall(uint256 _blockchainId,
address _contract,
bytes calldata _functionCallData)
external;
}
Function crossBlockchainCall
: Call a function on another blockchain.
Parameters:
_blockchainId
: Blockchain identifier of remote blockchain to be
called
in EIP 3220 format._contract
: Address of the contract on the remote blockchain to
be called._functionCallData
: The function selector and parameter data
encoded using Application Binary Interface (ABI) ABI encoding rules.
Crosschain function call components need to determine the source blockchain and contract address on the source blockchain that initiated the crosschain function call. Functions in contracts on destination blockchains use this information to determine if the caller is authorised to execute the functionality in the function. To prevent possible attacks, these authentication parameters are provided as hidden parameters that exist outside the scope of a functions declared parameters. The parameters are appended to the call data of a function call by the function call component and extracted by the application.
R2
: Non-Atomic Function Call implementations MUST encode the
hidden authentication parameters using
the encodeNonAtomicAuthParams
function described below.
O1
: Non-Atomic Function Call Applications MAY decode the
parameters using the decodeNonAtomicAuthParams
function to
determine
which contract on which blockchain called the function being executed.
pragma solidity >=0.8;
abstract contract NonAtomicHiddenAuthParams {
function encodeNonAtomicAuthParams(
bytes memory _functionCall,
uint256 _sourceBlockchainId,
address _sourceContract
) internal pure returns (bytes memory) {
return bytes.concat(_functionCall, abi.encodePacked(_sourceBlockchainId, _sourceContract));
}
function decodeNonAtomicAuthParams()internal pure returns (
uint256 _sourceBlockchainId,
address _sourceContract) {
bytes calldata allParams = msg.data;
uint256 len = allParams.length;
assembly {
calldatacopy(0x0, sub(len, 52), 32)
_sourceBlockchainId := mload(0)
calldatacopy(12, sub(len, 20), 20)
_sourceContract := mload(0)
}
}
Function encodeNonAtomicAuthParams
: Append authentication parameters to
the
function selector and parameter data.
Parameters:
_functionCall
: ABI encoded function selector and parameters of fuction
to be called._sourceBlockchainId
: Blockchain identifier of the blockchain
that is calling the function, in EIP 3220 format._sourceContract
: The address of the contract that is calling the
function.Function decodeNonAtomicAuthParams
: Extract authentication parameters
from the
end of the call data
.
Parameters:
Returns:
_sourceBlockchainId
: Blockchain identifier of the blockchain that is
calling the function, in EIP 3220 format._sourceContract
: The address of the contract that is calling the
function.The sub-sections below describe Solidity interfaces and helper functions between the Crosschain Application Layer and the Crosschain Function Call Layer for Atomic Function Call implementations.
The Crosschain Function Call Interface is defined in Section 2.1.1.
R3
: Atomic Function Call implementations MUST support the
CrosschainFunctionCallInterface
interface.
The Crosschain locking interface defines the API to be used by lockable storage contracts to indicate to the function call layer that a contract has provisional updates due to the current crosschain function call.
R4
: Atomic Function Call implementations MUST support the
CrosschainLockingInterface
interface.
interface CrosschainLockingInterface {
/**
* Called by a lockable storage contract to indicate that it contains one
* or more storage locations that are being locked by the current function
* call.
*
* @param _contractToLock The address of the contract contained the locked
* storage locations.
*/
function addToListOfLockedContracts(address _contractToLock) external;
/**
* Get the combined Root Blockchain / Crosschain Transaction id for the
* current crosschain transaction.
*
* @return Crosschain Root Blockchain / Transaction Id. The value is zero
* if there is no active crosschain call (and this is a single
* blockchain function call).
*/
function getActiveCallCrosschainRootTxId() external view returns (bytes32);
function isSingleBlockchainCall() external view returns (bool);
}
Crosschain Function Call Interface allows applications to call functions on other blockchains. The calls defined in this file return values.
D1
: Atomic Function Call implementations that allow values to be returned
SHOULD
support the
CrosschainFunctionCallReturnInterface
interface.
interface CrosschainFunctionCallReturnInterface is
CrosschainFunctionCallInterface
{
/**
* Call a function on another blockchain that returns a uint256 value. Function call implementations
* may implement this function. Implementations that do not support this functionality should revert
* with the message, "NOT SUPPORTED: crossBlockchainCallReturnsUint256".
*
* @param _bcId Blockchain identifier of blockchain to be called.
* @param _contract The address of the contract to be called.
* @param _functionCallData The function selector and parameter data encoded using ABI encoding rules.
*/
function crossBlockchainCallReturnsUint256(
uint256 _bcId,
address _contract,
bytes calldata _functionCallData
) external returns (uint256);
}
Lockable storage interface defines the API between the function call layer and the application layer to allow the function call layer to indicate to the application layer that locked values should be committed or discarded. Lockable storage implementations need to implement this interface.
R4
: Atomic Function Call implementations MUST support the
LockableStorageInterface
interface.
interface LockableStorageInterface {
/**
* Called by the crosschain control contract when the call has been completed.
*
* @param _commit True if the provisional updates should be committed. False indicates the
* provisional updates should be discarded.
* @param _crossRootTxId a value that indicates the transaction that has been completed.
* The value is the keccak256 message digest of the Root Blockchain Id and the
* Crosschain Transaction Id.
*/
function finalise(bool _commit, bytes32 _crossRootTxId) external;
}
Crosschain function call components need to determine the root blockchain, source blockchain and contract address on the source blockchain that called the function. Functions in contracts on destination blockchains use this information to determine if the caller is authorised to execute the functionality in the function. To prevent possible attacks, these authentication parameters are provided as hidden parameters that exist outside the scope of a functions declared parameters. The parameters are appended to the call data of a function call by the function call component and extracted by the application.
R5
: Atomic Function Call implementations MUST encode the hidden
authentication
parameters using the encodeAtomicAuthParams
function described below.
O2
: Atomic Function Call Applications MAY decode the parameters using the
decodeAtomicAuthParams
function to determine
which contract on which blockchain called the function being executed.
Conforming implementations should encode the hidden authentication parameters using the
encodeAtomicAuthParams
function described below. Applications should decode
the
parameters using the decodeAtomicAuthParams
function.
/**
* Add authentication parameters to the end of an existing function call.
*
* @param _functionCall Function selector and an arbitrary list of parameters.
* @param _rootBlockchainId Blockchain identifier of the root blockchain of the call execution tree.
* @param _sourceBlockchainId Blockchain identifier of the blockchain that is calling the function.
* @param _sourceContract The address of the contract that is calling the function.
*/
function encodeAtomicAuthParams(
bytes memory _functionCall,
uint256 _rootBlockchainId,
uint256 _sourceBlockchainId,
address _sourceContract
) internal pure returns (bytes memory) {
return
bytes.concat(
_functionCall,
abi.encodePacked(
_rootBlockchainId,
_sourceBlockchainId,
_sourceContract
)
);
}
/**
* Extract authentication values from the end of the call data. The parameters are expected to have been
* added to the end of the function call using encodeNonAtomicAuthParams.
*
* @return _rootBlockchainId Blockchain identifier of the root blockchain of the call execution tree.
* @return _sourceBlockchainId Blockchain identifier of the blockchain that is calling the function.
* @return _sourceContract The address of the contract that is calling the function.
*/
function decodeAtomicAuthParams()
internal
pure
returns (
uint256 _rootBlockchainId,
uint256 _sourceBlockchainId,
address _sourceContract
) {
bytes calldata allParams = msg.data;
uint256 len = allParams.length;
assembly {
calldatacopy(0x0, sub(len, 84), 32)
_rootBlockchainId := mload(0)
calldatacopy(0x0, sub(len, 52), 32)
_sourceBlockchainId := mload(0)
calldatacopy(12, sub(len, 20), 20)
_sourceContract := mload(0)
}
}
This section defines requirements specific to each supported non-atomic function call protocol.
The description of APIs and processing for the protocol will be finalised after work on GPACT has been completed.
This section defines requirements specific to each supported atomic function call protocol.
The General Purpose Atomic Crosschain Transaction (GPACT) protocol is an atomic crosschain transaction protocol. The protocol is similar to a two-phase commitment protocol. A call execution tree is committed to, then parts of the call execution tree are executed on each blockchain, resulting in provisional updates. Finally, provisional updates are committed if all parts of the execution are successful, and discarded otherwise.
The
diagram at this link
shows an example flow for the protocol. In the example, a function,
doStuffSource
in contract AppSourceChain.sol
on
Chain A
calls doStuffTarget
in contract
AppSourceTarget.sol
on Chain B
. The execution of either or
both
functions could result in updates to storage values. The function
doStuffTarget
could return a value to doStuffSource
. It should
be noted that this is a simplistic example. The protocol allows for arbitrary call
execution. That is, one function may call multiple functions on other blockchains. Those
functions may in turn call other functions on other blockchains.
The example has an applications that uses GPACT for the Crosschain Function Call Layer implementation and Event Attestation for the Crosschain Messaging Layer implementation.
Walking through the sequence diagram:
start
function. The start
function emits a
Start Event
. The Start Event
contains the encoded call
execution tree, thus locking in the call tree that will be executed.
Chain A
to fetch
the
multiply signed event. For block header transfer mechanisms, the Relayer /
Attestor nodes would have forward the block header to the destination chain and
would return a Merkle proof showing that the event was part of a transaction
receipt that was part of the block related to the transferred block header.
Start Event
and call path
for the
segment that is to be executed on Chain B
, by calling the
Crosschain Control Contract on Chain B
's segment
function.
doStuffTarget
, and emits a Segment Event
indicating the contracts that contain locked storage locations, and the return
value from the function.
Chain B
to fetch
the
multiply signed event.Start Event
and
Segment Event
to the Crosschain Control Contract on
Chain A
's root
function. This requests that the entry
point function for the call tree be executed.
Chain A
and Chain B
(for the Start
and Segment Event
s respectively). It then executes the application
function doStuffSource
, and emits a Root Event
indicating that the overall crosschain function call has been successful
(or not). Root Event
.Root Event
and
Segment Event
to the Crosschain Control Contract on
Chain B
's signalling
function. This requests that the
contracts listed in the Segment Event
have the provisional updates
that relate to this crosschain transaction committed or discarded, based on the
information in the Root Event
.
Chain A
and
Chain B
(for the
Root
and Segment Event
s respectively).
It then calls the finalise
function on each of the contracts
indicated in the
Segment Event
to commit or discard the provisional updates. For
this example, the contract is the AppTargetChain.sol
.
The start
function is used to register a crosschain function call with the
GPACT protocol. It is the first function call of the protocol. It is called on the root
blockchain of the call tree.
R6
: GPACT implementations MUST implement the start
function
described below.
R7
: GPACT implementations start
functions MUST emit a start
event as
described below.
function start(
uint256 _crossBlockchainTransactionId,
uint256 _timeout,
bytes calldata _callTree
)
Where:
_crossBlockchainTransactionId
: An identifier that must be unique for
transactions initiated from this blockchain._timeout
: Period time in seconds after which this crosschain
transaction will be deemed to have timed-out._callTree
: Encoded arbitrary call tree. See below for the details of
this
encoding.Call Trees are encoded data structures that represent the entry-point function calls to each blockchain that are expected to be called in a crosschain function call. The format of Call Trees is described below.
The encoding uses as a base type an EncodedFunction
. The definition of this type
is:
abi.encode(struct {
uint256 blockchainId;
uint160 contractAddress;
bytes callData;
}) EncodedFunction
Where:
abi.encode
blockchainId
contractAddress
callData
The encoding of the a call tree is:
struct {
uint8 numCalledFunctions: (0 == Leaf Function, 1..255: Non-Leaf Function)
IF (numCalledFunctions == Leaf Function) {
EncodedFunction leafFunction
}
else {
uint32[numCalledFunctions+1] startOffets
EncodedFunction callingFunction
CallTree[numCalledFunctions] calledFunctions
}
}) CallTree
Where:
numCalledFunctions
: The number of functions the function represented at
this level of the call tree calls. If the function is a leaf function, it calls
not other functions. In this case, numCalledFunctions
will be zero.
leafFunction
: The encoding of the function to be called.startOffsets
: provides start offsets of the
callingFunction
and calledFunctions
relative to the
start of the CallTree
object.
callingFunction
: The function that is calling other functions.calledFunctions
: An array of CallTree
objects.This structure has been used to allow EncodedFunction objects to be efficiently extracted from multi-level call trees.
NOTE: The maximum number of functions called by a calling function at any level of the call tree is 255.
The encoding of the a versioned call tree, the _callTree
parameter from the
function above is:
struct {
uint16 type = 0x0001;
CallTree callTree;
} VersionedCallTree
Where:
type
: Always 0x0001
for this format of call tree.
callTree
: The call tree to be executed.The start event is defined as:
event Start(
uint256 _crossBlockchainTransactionId,
address _caller,
uint256 _timeout,
bytes _callTree
);
Where:
_crossBlockchainTransactionId
: The parameter of the same name from the
start call._caller
: The externally owned account that called the start function.
_timeout
: The parameter of the same name from the start call._callTree
: The parameter of the same name from the start call.The segment
function call is used to execute a part of the call tree. Call trees
are executed from leaf function to root, thus allowing the results of one segment to
feed into the segment closer to the root call, and ultimately to the root call.
R7
: GPACT implementations MUST implement the segment
function
described below.
function segment(
uint256[] calldata _blockchainIds,
address[] calldata _cbcAddresses,
bytes32[] calldata _eventFunctionSignatures,
bytes[] calldata _eventData,
bytes[] calldata _signaturesOrProofs,
uint256[] calldata _callPath
)
The segment function takes arrays of blockchain identifiers, crosschain control contract addresses, event signatures, event data, and proofs or signatures. These arrays correspond to events that were emitted on a certain blockchain by a certain crosschain control contract with a certain event signature and event data. The signatures or proofs are the information that will allow the event to be trusted on the blockchain the segment is executing on.
The first event is a start event. Subsequent events are the segment events matching the segments called from the function being executed, in execution order.
Where:
_blockchainIds
: Array of blockchain idenfiers that events were emitted
on._cbcAddresses
: Array of addresses of crosschain control contracts that
emitted events._eventFunctionSignatures
: Array of event function signatures for
emitted events._eventData
: ABI encoded parameter information of the emitted event.
_signaturesOfProofs
: Signatures or proofs encoded based on the
Crosschain Messaging Protocol used to prove the validity of the emitted event.
_eventData
: ABI encoded parameter information of the emitted event.
_callPath
: An indication of the part of the call tree to be executed.
The format of this object is defined below.
Call Paths are used to identify function calls within a call tree. They are used to indicate to the Cross Blockchain Control contract which function should be being executed. Only functions that are entry point functions for a blockchain are counted in the Call Path.
Call Paths are arrays of unsigned integers. The size of the array indicates how deep the call is in the call tree. If the final element of the array is zero, it indicates that the function calls other functions via crosschain calls. Otherwise, it indicates that the function does not execute crosschain calls.
This means that the root function call for a call tree has a path encoding of
{0}
indicating that it is a function that calls other functions on other blockchains
via crosschain calls.
The diagram below shows functions in contracts on blockchains that call
other contracts. For example, function a1a
on Blockchain A in
Contract A1 calls function function b1a
on Blockchain B in
Contract B1. This function call could be written:
function a1a -> function b1a
.
Blockchain A {
Contract A1 {
function a1a() {
calls B.b1.b1a()
}
function a1b() {
calls B.b2.b2a()
calls C.c1.c1a()
calls B.b2.b2b()
}
}
Contract A2 {
function a2a() {
calls C.c2.c2a()
}
}
}
Blockchain B {
Contract B1 {
function b1a() {}
}
Contract B2 {
function b2a() {}
function b2b() {
calls C.c1.c1a()
calls C.c2.c2a()
calls A.a2.a2a()
}
}
}
Blockchain C {
Contract C1 {
function c1a() {}
}
Contract C2 {
function c2a() {}
}
}
Assuming that the root function is A.a1.a1a()
and that all functions in the call
tree
are called, then the call paths are shown in the table below.
Function | Call Path | Encoded Call Path |
---|---|---|
A.a1.a1a() | A.a1.a1a() | {0} |
B.b1.b1a() | A.a1.a1a() -> B.b1.b1a() | {1} |
Assuming that the root function is A.a1.a1b()
and that all functions in the call
tree
are called, then the call paths are shown in the table below.
Function | Call Path | Encoded Call Path |
---|---|---|
A.a1.a1b() | A.a1.a1b() | {0} |
B.b2.b2a() | A.a1.a1b() -> B.b2.b2a() | {1} |
C.c1.c1a() | A.a1.a1b() -> C.c1.c1a() | {2} |
B.b2.b2b() | A.a1.a1b() -> B.b2.b2b() | {3, 0} |
C.c1.c1a() | A.a1.a1b() -> B.b2.b2b() -> C.c1.c1a() | {3, 1} |
C.c2.c2a() | A.a1.a1b() -> B.b2.b2b() -> C.c2.c2a() | {3, 2} |
A.a2.a2a() | A.a1.a1b() -> B.b2.b2b() -> A.a2.a2a() | {3, 3, 0} |
C.c2.c2a() | A.a1.a1b() -> B.b2.b2b() -> A.a2.a2a() -> C.c2.c2a() | {3, 3, 1} |
The segment event is defined as:
event Segment(
uint256 _crossBlockchainTransactionId,
bytes32 _hashOfCallTree,
uint256[] _callPath,
address[] _lockedContracts,
bool _success,
bytes _returnValue
);
Where:
_crossBlockchainTransactionId
: The crosschain transaction identifier.
_hashOfCallTree
: Keccak256 message digest of the call tree. _callPath
: The part of the call tree this segment pertains to._lockedContracts
: Array of addresses of contracts that have one or more
storage locations locked._success
: true if this segment executed successfully._returnValue
: ABI encoded function return result.The root
function call is used to execute the root of the call tree. This
function is either called after all other segments functions have been successfully
called, after a segment has failed, or after the crosschain transaction has timed-out.
R7
: GPACT implementations MUST implement the root
function described below.
function root(
uint256[] calldata _blockchainIds,
address[] calldata _cbcAddresses,
bytes32[] calldata _eventFunctionSignatures,
bytes[] calldata _eventData,
bytes[] calldata _signaturesOrProofs
)
Similar to the segment function, the root function takes arrays of blockchain identifiers, crosschain control contract addresses, event signatures, event data, and proofs or signatures. These arrays correspond to events that were emitted on a certain blockchain by a certain crosschain control contract with a certain event signature and event data. The signatures or proofs are the information that will allow the event to be trusted on the blockchain the segment is executing on.
The first event is a start event. Subsequent events are the segment events matching the segments called from the function being executed, in execution order.
Where:
_blockchainIds
: Array of blockchain idenfiers that events were emitted
on._cbcAddresses
: Array of addresses of crosschain control contracts that
emitted events._eventFunctionSignatures
: Array of event function signatures for
emitted events._eventData
: ABI encoded parameter information of the emitted event.
_signaturesOfProofs
: Signatures or proofs encoded based on the
Crosschain Messaging Protocol used to prove the validity of the emitted event.
_eventData
: ABI encoded parameter information of the emitted event.
The root event is defined as:
event Root(
uint256 _crossBlockchainTransactionId,
bool _success
);
Where:
_crossBlockchainTransactionId
: The crosschain transaction identifier.
_success
: true if the crosschain transaction has been successful and
all updates on all blockchains should be committed.The signalling
function call is used to commit or discard provisional updates on
blockchains that segment functions executed on and locked storage. This function must be
called after the root function has been called.
R8
: GPACT implementations MUST implement the signalling
function described below.
function signalling(
uint256[] calldata _blockchainIds,
address[] calldata _cbcAddresses,
bytes32[] calldata _eventFunctionSignatures,
bytes[] calldata _eventData,
bytes[] calldata _signatures
)
Similar to the segment function, the signalling function takes arrays of blockchain identifiers, crosschain control contract addresses, event signatures, event data, and proofs or signatures. These arrays correspond to events that were emitted on a certain blockchain by a certain crosschain control contract with a certain event signature and event data. The signatures or proofs are the information that will allow the event to be trusted on the blockchain the segment is executing on.
The first event is the root event. Subsequent events are the segment events for segments executed on this blockchain that locked storage.
Where:
_blockchainIds
: Array of blockchain idenfiers that events were emitted
on._cbcAddresses
: Array of addresses of crosschain control contracts that
emitted events._eventFunctionSignatures
: Array of event function signatures for
emitted events._eventData
: ABI encoded parameter information of the emitted event.
_signaturesOfProofs
: Signatures or proofs encoded based on the
Crosschain Messaging Protocol used to prove the validity of the emitted event.
_eventData
: ABI encoded parameter information of the emitted event.
Similar to existing single blockchain applications, the type of application
authentication is
application specific. Applications that checked msg.sender
in a
single
blockchain context should use the decodeNonAtomicAuthParams
and
decodeAtomicAuthParams
to check that the source blockchain and
source contract are authorised to call the function. In the case of atomic
crosschain function calls, the application should also check that the root
blockchain
of the call execution tree is authorised. Applications should limit which
blockchains can be root blockchains to those blockchains that they have access
to.
A requirement is uniquely identified by a unique ID composed of its requirement level followed by a requirement number, as per convention [RequirementLevelRequirementNumber]. There are four requirement levels that are coded in requirement ids as per below convention:
R
is to be interpreted as MUST as described
in RFC2119.D
is to be interpreted as SHOULD as described in
RFC2119.
O
is to be interpreted as MAY as described in RFC2119.
Note that requirements are uniquely numbered in ascending order within each requirement level.
Example : It should be read that [R1] is an absolute requirement of the specification whereas [D1] is a recommendation and [O1] is truly optional.
Conditional requirements are expressed as per convention [ConditionalRequirementID] < [ControllingRequirementID]. A conditional requirement becomes required if and only if its controlling requirement is implemented.
For instance [CR1] < [D3] means that if recommended requirement [D3] is implemented then it is required to also implement requirement [CR1].
[eeaciw-crosschainspec-v1.0] EEA CIW - osschain Interoperability Specification Draft Version 1.0. Edited by Weijia Zhang, Peter Robinson and Aiman Baharna. 28 February 2022. EEA CIW. https://entethalliance.github.io/crosschain-interoperability/draft_crosschain_techspec.html . Latest stage: https://entethalliance.github.io/crosschain-interoperability/draft_crosschain_techspec.html .
[eeaciw-crosschainidentification-v1.0] EEA CIW - Crosschain Identification Specification Version 1.0. Edited by Weijia Zhang and Peter Robinson. 14 December 2020. EEA CIW. https://entethalliance.github.io/crosschain-interoperability/crosschainid.html . Latest stage: https://entethalliance.github.io/crosschain-interoperability/crosschainid.html .
[eeaciw-messaging-v1.0] EEA CIW - Technical Specification Messaging Interface Draft Version 1.0. Edited by Weijia Zhang, Peter Robinson and Aiman Baharna. 28 February 2022. EEA CIW. https://entethalliance.github.io/crosschain-interoperability/draft_crosschain_techspec_messaging.html . Latest stage: https://entethalliance.github.io/crosschain-interoperability/draft_crosschain_techspec_messaging.html .
The EEA acknowledges and thanks the many people who contributed to the development of this draft version of the specification.
Enterprise Ethereum is built on top of Ethereum, and we grateful to the entire community who develops Ethereum, for their work and their ongoing collaboration to helps us maintain as much compatibility as possible with the Ethereum ecosystem.