1

I want to write a smart contract that:

  1. Stores education certificates within the contact, and

  2. Allow the users to search these records.

Each certificate is represented by a struct, Cert.

struct Cert {
 uint256 ipfsId;
 address issuerAddress;
 address recipientAddress;
}

Once a struct is created I store them in maps so it's easier to retrieve them.

The user can retrieve certificates base on the certificate's ID, the issuer's address, or the receiver's address.

mapping (uint256 => Cert) certificates;
mapping (address => uint256) certificatesByIssuer;
mapping (address => uint256) certificatesByRecipient;

My issues are with the maps certificatesByIssuer and certificatesByRecipient.

  1. Both maps can have duplicate keys if the same issuer issued multiple certificates

  2. I can only retrieve one certificate at a time from the map which can be a problem if the same issuer issued multiple certificates.

Question:

  1. Is there a way to avoid key duplication in my case? (For instance, switching the uint256 variable in both maps to a dynamic array, but how would I update the array then?)

  2. Even if I resolve the issue in part 1, is it possible to retrieve multiple entries from the map all at once? (I plan to let the user interact with the contract through HTML browser interface.)

2

There is probably more than one way to solve this. On first inspection, this strikes me as a data organization issue.

Questions:

  1. You can't use mappings without first eliminating the possibility of key duplication.

  2. I don't recommend retrieving more than one record at a time even in the case where a list would be the obvious response. You can stuff dynamic arrays inside mapped structs. Indeed, you will have to in order to fully describe the associations.

The possibility of side-stepping the issue:

You don't necessarily have to enforce the joins in the contract and you don't necessarily have to satisfy all possible queries with an on-chain solution. A server-side cache or a client assembling its own cache can solve a lot of problems. It may be enough that correctness can be verified on-chain.

When to enforce referential integrity:

Strict on-chain enforcement of the joins is something to consider if safeguarding the integrity of the data is important. For instance, ensuring only known issuers publish certificates.

A "rabbit hole" warning:

I have no way to explain/rationalize everything in a short answer so I'm going to refer you to Part 3 in a series. You might have to review parts 1 & 2 to understand how everything works. The pattern would solve for enforcement of the joins and discovering query results in a system of 3 (or more) entities with strict join types.

https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42

Some of the syntax (throw) is a little outdated. The basic idea is you need to create a two-way binding between the entities and maintain it as you update the data. You've got three entities (recipient, issuer and certificate). You can put lists of certificates in recipients and issuers. Such a data organization makes it possible to enumerate the query results you're after, one row at a time.

Hope it helps.

  • Hi Rob, thanks for the reply. I think you're right that I can leave some implementations off-chain and use only Ethereum to maintain data correctness. It saves memory for Ethereum and gives off-chain developers more flexibility. Sometimes less is more. Appreciate the links as well. – potatoguy Nov 26 '17 at 13:32
  • Thanks for the upvote. You can also accept the answer if you think it clears up the doubts. – Rob Hitchens - B9lab Nov 26 '17 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.