/
Open in Online IDE

Attachments

Attaching files to transactions


Reading Time: 6 min

There are cases where network participants need to reuse a certain piece of data. Corda facilitates that task by allowing to add that piece of data as an attachment to the transaction. When a party receives that transaction, they can then request the attachment file from the sender.

Attachments are limited to zip and jar files for the following reasons:

  1. To avoid transferring large files over the wire.
  2. To allow the execution of some of those attachments, in the case of jar files.
  3. jar files can be signed with the jarsigner tool, making it possible to restrict acceptable attachments to a certain list of signing keys.

Obviously, you can attach any file type as long as you place it inside a zip or a jar file. Of note is that jar files are essentially fancy zip files themselves.

Among its many moving pieces, a Corda node also has an attachment store where it keeps such things. Those attachments are not referenced by a file name or other external id. Instead they are referenced by their SHA256 content hash. This is another example of a content-addressable store. As with so many other aspects of DLT, it prevents tampering.

So yes, attaching a file to a transaction is no different than adding its hash.

Aside from the type of file attached, attachments can be grouped into 2 major logical groups, contract jar files and general files.

Contracts jar files

As you know, when a transaction is created, a contract must be specified for each state type that is part of said transaction.

  • The contracts' role is to validate the state transitions that the transaction represents.
  • This validation needs to be deterministically reproducible, now and in the future, which requires the exact contracts that were used during the initial finalisation.

It is, therefore, to facilitate reproduceability that contract jars are attached to transactions. So when a responding node calls ReceiveFinalityFlow to finalize the received transaction, it in turn executes the pre-uploaded contracts found in the relevant jar files.

Indeed, given the risk of mischief, contracts are pre-uploaded and not downloaded automatically to the node. You got a taste of that when you removed a jar file from one of the nodes and then ran the IOU CorDapp.

It's worth mentioning that not any uploaded jar file will be accepted as a valid contract container either. There are various contract constraints that control what gets accepted.

General files

General files can be any piece of data that's being shared and reused by the network participants. For instance, the samples repository has an example where an attachment file contains the names of blacklisted parties. So those parties cannot participate in an agreement.

  • The file is defined here.

  • If you unzip blacklist.jar, you get:

    $ unzip blacklist.jar
    Archive: blacklist.jar
     extracting: anotherfile.txt
      inflating: blacklist.txt
  • Also calculate its SHA256 hash:

    $ shasum -a 256 blacklist.jar
    4cec607599723d7e0393eb5f05f24562732cd1b217deaedeabd4c25afe5b333a  blacklist.jar

    Which, thankfully, matches the value hard-coded in the contract.

  • On the contract, see that it expects a single extra attachment.

  • That should also be of the expected hash.

  • That iuses some facility given by Corda to extract the desired file, then builds the list of blacklisted companies.

  • With that, it makes sure that there is no overlap with the transaction participants.

In this example, the attachment's content is used in the code. Of course using the content of a file in your contract code is not a necessity. You are free to add an attachment for serious purposes such as the contract legal prose, or for frivolous reasons.

Manipulating Attachments

We have seen that attachments are added by hash. And the nodes have a hash-indexed attachment store. This store can receive new attachments, either when instructed by the administrator, or when a new transaction mentions an unknown hash. The latter triggers a download request to the node that submitted the transaction.

Here is a list of the most common attachment tasks.

Direct upload

You can upload an attachment manually or programmatically.

  • To do it manually, simply run one of the following commands from within your node's shell.

    >>> run uploadAttachment jar: path/to/the/file.jar

    Or, the variation that allows you to specify the uploader and filename metadata, which you can later use to search for this attachment.

    >>> run uploadAttachmentWithMetadata jar: path/to/the/file.jar, uploader: myself, filename: original_name.jar

    Either operation will return you the hash string that you can use at a later stage.

  • To do it programmatically, use the ServiceHub attachments functionality, typically, within a flow.

    final SecureHash attachmentHash = getServiceHub().getAttachments().importAttachment(
            new FileInputStream(new File("path/to/file")), "uploader_name", "filename");

Addition to a transaction

This is really simple when you have the attachmentHash. If you only have the string of the hash, use:

final Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
final TransactionBuilder transactionBuilder = new TransactionBuilder(notary);

// Add attachment to transaction.
final SecureHash attachmentHash = SecureHash.parse(hashString);
transactionBuilder.addAttachment(attachmentHash);

What's in my attachment store?

When searching for an attachment, you can do it either by its hash or by its metadata, and you can do either in your node's shell or from within a flow:

  • To search by id:

    // From inside of a node's shell.
    >>> run openAttachment id: AB7FED7663A3F195A59A0F01091932B15C22405CB727A1518418BF53C6E6663A
    // From inside of a flow.
    final Attachment attachment = getServiceHub().getAttachments().openAttachment(SecureHash.parse("Id"));
    
    // You can also use "openAsJar()" if it's a jar file.
    final InputStream content = attachment.open();
  • To search by metadata, AttachmentQueryCriteria comes into play. You need to add a new dependency, and then you can have multiple criterias and combine them with .and, .or, .like, etc... The dependency is:

    dependencies {
        cordaCompile "$corda_release_group:corda-node:$corda_release_version"

    Then within a flow:

    final FieldInfo attributeUploader = QueryCriteriaUtils.getField(
            "uploader", NodeAttachmentService.DBAttachment.class);
    final FieldInfo attributeFilename = QueryCriteriaUtils.getField(
            "filename", NodeAttachmentService.DBAttachment.class);
    
    final ColumnPredicate<String> uploaderPredicate = Builder.equal(attributeUploader, "me").component2();
    final ColumnPredicate<String> filenamePredicate = Builder.equal(attributeFilename, "my-file.zip").component2();
    
    final AttachmentsQueryCriteria attachmentsCriteria = new AttachmentsQueryCriteria()
            .withUploader(uploaderPredicate)
            .withFilename(filenamePredicate);
            /*
             You can also query by:
                .withContractClassNames()
                .withSigners()
                .withUploadDate()
                .withVersion()
            */
    
    final List<SecureHash> results = getServiceHub().getAttachments().queryAttachments(attachmentsCriteria);

In a contract

You already saw these actions in the blacklist sample, so to repeat:

final Attachment attachment = tx.getAttachment(SecureHash.parse("id"));
// You can also use "openAsJar()" if it's a jar file.
final InputStream content = attachment.open();

Conclusion

You have seen that transactions have attachments, and that nodes store and exchange them on the side. There is no guided exercise.

Discuss on Slack
Rate this Page
Would you like to add a message?
Submit
Thank you for your Feedback!