Transaction tear-offs¶
Example of usage¶
Let’s focus on a code example. We want to construct a transaction with commands containing interest rate fix data as in:
Writing oracle services. After construction of a partial transaction, with included Fix
commands in it, we want to send it
to the Oracle for checking and signing. To do so we need to specify which parts of the transaction are going to be
revealed. That can be done by constructing filtering function over fields of WireTransaction
of type (Any) ->
Boolean
.
val partialTx = ...
val oracle: Party = ...
fun filtering(elem: Any): Boolean {
return when (elem) {
is Command -> oracleParty.owningKey in elem.signers && elem.value is Fix
else -> false
}
}
Assuming that we already assembled partialTx with some commands and know the identity of Oracle service, we construct
filtering function over commands - filtering
. It performs type checking and filters only Fix
commands as in
IRSDemo example. Then we can construct FilteredTransaction
:
val wtx: WireTransaction = partialTx.toWireTransaction()
val ftx: FilteredTransaction = wtx.buildFilteredTransaction(filtering)
In the Oracle example this step takes place in RatesFixFlow
by overriding filtering
function, see:
Using an oracle.
FilteredTransaction
holds filteredLeaves
(data that we wanted to reveal) and Merkle branch for them.
// Direct accsess to included commands, inputs, outputs, attachments etc.
val cmds: List<Command> = ftx.filteredLeaves.commands
val ins: List<StateRef> = ftx.filteredLeaves.inputs
val timestamp: Timestamp? = ftx.filteredLeaves.timestamp
...
fun sign(ftx: FilteredTransaction): DigitalSignature.WithKey {
if (!ftx.verify()) {
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
// Performing validation of obtained FilteredLeaves.
fun commandValidator(elem: Command<*>): Boolean {
if (!(identity.owningKey in elem.signers && elem.value is Fix))
throw IllegalArgumentException("Oracle received unknown command (not in signers or not Fix).")
val fix = elem.value as Fix
val known = knownFixes[fix.of]
if (known == null || known != fix)
throw UnknownFix(fix.of)
return true
}
fun check(elem: Any): Boolean {
return when (elem) {
is Command<*> -> commandValidator(elem)
else -> throw IllegalArgumentException("Oracle received data of different type than expected.")
}
}
val leaves = ftx.filteredLeaves
if (!leaves.checkWithFun(::check))
throw IllegalArgumentException()
// It all checks out, so we can return a signature.
//
// Note that we will happily sign an invalid transaction, as we are only being presented with a filtered
// version so we can't resolve or check it ourselves. However, that doesn't matter much, as if we sign
// an invalid transaction the signature is worthless.
val signature = services.keyManagementService.sign(ftx.rootHash.bytes, signingKey)
return DigitalSignature.WithKey(signingKey, signature.bytes)
}
Above code snippet is taken from NodeInterestRates.kt
file and implements a signing part of an Oracle. You can
check only leaves using leaves.checkWithFun { check(it) }
and then verify obtained FilteredTransaction
to see
if data from PartialMerkleTree
belongs to WireTransaction
with provided id
. All you need is the root hash
of the full transaction:
if (!ftx.verify(merkleRoot)){
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
Or combine the two steps together:
ftx.verifyWithFunction(merkleRoot, ::check)
Note
The way the FilteredTransaction
is constructed ensures that after signing of the root hash it’s impossible to add or remove
leaves. However, it can happen that having transaction with multiple commands one party reveals only subset of them to the Oracle.
As signing is done now over the Merkle root hash, the service signs all commands of given type, even though it didn’t see
all of them. This issue will be handled after implementing partial signatures.