Minka Ledger Docs
Explanations

About Labels

DateResponsibleChanges
July 10, 2024@Luis FidelisInitial version

This document describes the concept of labels and helps users leveraging its use in their use cases.

What is a label?

Label - meta.labels - is a property of records that allows users to attach a label or tag anything in ledger with a descriptive name. Some examples of the usage of labels in ledger:

  • preferred accounts: marking certain wallets or anchors as preferred.
  • organizing records: marking wallets as saving accounts, cards, loans, etc.
GET /v2/anchors/anchor
 
{
    hash: '...',
    data: {
        handle: 'anchor',
        wallet: 'anchor-wallet',
        target: 'target-wallet',
        symbol: 'usd',
        ...
    },
    meta: {
        ...,
        labels: ['preferred']
    },
    ...
}
GET /v2/wallets/saving-wallet 
 
{
    hash: '...',
    data: {
        ...,
        handle: 'saving-wallet',
    },
    meta: {
        ...,
        labels: ['saving-account']
    },
    luid: '$wlt.ijidjiddpsdpl'
}

The regular custom fields can be used to accomplish many of those use cases as well and they are preferable in most situations. Labels can be an alternative when it makes sense in same ways you can use tags in many apps to organize and group.

Labels vs Custom

Even though custom is applicable for most of the cases, labels can have uniqueness constraints defined via policies in ledger.

Let's dive into the example of marking a preferred account in the system.

An account could be marked as preferred by having a preferred property in custom, i.e.

{
    ...
    handle: 'anchor',
    wallet: 'anchor-wallet',
    target: 'target-wallet',
    symbol: 'usd',
    custom: {
        preferred: true
    },
}

but this basic approach is very limited in several main ways.

  1. The first issue is that you can have only one preferred account, sometimes you want to have a preferred per symbol, this becomes difficult to manage with custom .
  2. The second issue is related to security, marking something as preferred requires you to have full access to manage that record in ledger, since you need to update it to set a value.
  3. The last main issue is related to data consistency but it overlaps a bit with the security aspect. The issue is related to removing the preferred field from the record that was a preferred previously. This can either cause multiple records being marked as preferred or requires someone to have access to both the new and the currently preferred records. Often this is very difficult to implement while keeping everything secure and protected.

Managing record labels

Users can manage labels of a record within a proof. Proofs with labels can be attached to records when they are created - POST /{record}s e.g. POST /anchors - or with a separate call to

POST /{record}s/{id}/proofs e.g. POST /anchors/anchor-handle/proofs.

POST /v2/wallets
 
{
    hash: '...',
    data: {
        ...,
        handle: 'saving-wallet',
    },
    meta: {
        ...,
        proofs: [{
            method: 'ed25519-v2',
            public: '<signer public>',
            digest: '<proof digest>',
            result: '<signature result>',
            custom: {
                status: 'created',
                moment: '2024-07-10T00:00:00',
                labels: ['saving-account']
            }
        }]
    }
}
POST /v2/wallets/saving-wallet/proofs
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>',
    custom: {
        moment: '2023-11-27T17:18:13.034Z',
        labels: ['saving-account']
    }
}

A proof can push, pull and make complex changes in a record labels list.

Below is extensive list of expressions the ledger supports for changing record labels:

// 1. Append label "preferred"
"labels": { "$push": "preferred" }
 
// 2. Append labels "preferred" and "verified"
"labels": { "$push": { "$each": ["preferred", "verified"] } }
 
// 3. Insert labels "preferred" and "verified" before all other labels
"labels": { "$push": { "$each": ["preferred", "verified"], "$position": 0 } }
 
// 4. Insert labels "preferred" and "verified" before all other labels
//    and limit the resulting labels count to 3 by removing labels 
//    from the tail
"labels": { "$push": { "$each": ["preferred", "verified"], "$position": 0, "$slice": 3 } }
 
// 5. Append labels "preferred" and "verified" and sort the resulting labels
//    descending
"labels": { "$push": { "$each": ["preferred", "verified"], "$sort": -1 } }
 
// 6. Append label "preferred" unless it's already present
"labels": { "$addToSet": "preferred" }
 
// 7. Append labels "preferred" and "verified", each label will be appended
//    independently unless it's already present
"labels": { "$addToSet": { "$each": ["preferred", "verified"] } }
 
// 8. Remove label "preferred" if present
"labels": { "$pull": "preferred" }
 
// 9. Remove labels "preferred" and "verified" if present
"labels": { "$pull": { "$in": ["preferred", "verified"] } }
 
// 10. Remove labels "preferred" and "verified" if present, the same result
// as previous one but different operator
"labels": { "$pullAll": ["preferred", "verified"] }
 
// 11. Remove the first label
"labels": { "$pop": -1 }
 
// 12. Remove the last label
"labels": { "$pop": 1 }

The simple array expression from above which replaces the labels array in record completely can be also expressed as complex expression. So the following 2 have the same meaning:

// Replace all labels with array of 2 labels: "preferred" and "verified". It can
// be expressed in 2 ways.
 
// 1. Simple expression which sets labels to exact value
"labels": ["preferred", "verified"]
 
// 2. Complex expression: push "preferred" and "verified" but slice the resulting
//    labels so that it contains 2 items, by removing surplus prefix items.
"labels": { "$push": { "$each": ["preferred", "verified"], "$slice": -2 } }

Some examples:

Wallet before proof
{
    ...,
    meta: {
        ...,
        labels: ['saving-account', 'preferred']
    }
}
 
POST /v2/wallets/saving-wallet/proofs
 
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>'
    custom: {
        labels: { 
            $pull: 'saving-account' 
        },
        moment: '2023-11-27T17:18:13.034Z'
    }
}
 
Wallet after proof
{
    ...,
    meta: {
        ...,
        labels: ['preferred']
    }
}
Wallet before proof
{
    ...,
    meta: {
        ...,
        labels: ['saving-account', 'preferred', 'active']
    }
}
 
POST /v2/wallets/saving-wallet/proofs
 
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>'
    custom: {
        labels: { 
            $pullAll: [
                'saving-account', 
                'preferred'
            ] 
        },
        moment: '2023-11-27T17:18:13.034Z'
    }
}
 
Wallet after proof
{
    ...,
    meta: {
        ...,
        labels: ['active']
    }
}
Wallet before proof
{
    ...,
    meta: {
        ...,
        labels: ['active']
    }
}
 
POST /v2/wallets/saving-wallet/proofs
 
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>'
    custom: {
        labels: { 
            $push: {
                $each: ['saving-account', 'preferred'],
                $position: 0
            } 
        },
        moment: '2023-11-27T17:18:13.034Z'
    }
}
 
Wallet after proof
{
    ...,
    meta: {
        ...,
        labels: ['saving-account', 'preferred', 'active']
    }
}
Wallet before proof
{
    ...,
    meta: {
        ...,
        labels: ['active']
    }
}
 
POST /v2/wallets/saving-wallet/proofs
 
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>'
    custom: {
        labels: { 
            $addToSet: {
                $each: ['active', 'preferred']
            } 
        },
        moment: '2023-11-27T17:18:13.034Z'
    }
}
 
Wallet after proof
{
    ...,
    meta: {
        ...,
        labels: ['active', 'preferred']
    }
}
Wallet before proof
{
    ...,
    meta: {
        ...,
        labels: ['active', 'preferred']
    }
}
 
POST /v2/wallets/saving-wallet/proofs
 
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>'
    custom: {
        labels: { 
            $pop: 1
        },
        moment: '2023-11-27T17:18:13.034Z'
    }
}
 
Wallet after proof
{
    ...,
    meta: {
        ...,
        labels: ['active']
    }
}
Wallet before proof
{
    ...,
    meta: {
        ...,
        labels: ['active', 'preferred']
    }
}
 
POST /v2/wallets/saving-wallet/proofs
 
{
    method: 'ed25519-v2',
    public: '<signer public>',
    digest: '<proof digest>',
    result: '<signature result>'
    custom: {
        labels: { 
            $pop: -1
        },
        moment: '2023-11-27T17:18:13.034Z'
    }
}
 
Wallet after proof
{
    ...,
    meta: {
        ...,
        labels: ['preferred']
    }
}

Managing labels with policies

This section uses labels policies to define which labels are valid for a record as well as their uniqueness criteria. Please, read About Labels Policies for keeping up to date with this concept.

POST /v2/policies
 
{
    ...,
    data: {
        handle: 'preferred-account-anchor-per-wallet',
        schema: 'labels',
        record: 'anchor',
        filter: {
            schema: 'account'
        },
        values: [
            {
            labels: ['preferred'], 
            unique: ['wallet']
            }
        ],
        custom: {
            description: 'Marks a preferred account anchor per wallet'
        }
    }
}

Given the policy above, let’s look at an example when we have two account anchors on the same wallet:

[
    {
        handle: 'svgs.100100.mint',
        wallet: 'tel:1234',
        schema: 'account',
        target: 'svgs:100100@mint',
        symbol: 'usd'
    },
    {
        handle: 'svgs.200200.tesla',
        wallet: 'tel:1234',
        schema: 'account',
        target: 'svgs:200200@tesla',
        symbol: 'usd'
    }
]

First, we label the svgs.100100.min anchor as preferred:

POST /v2/anchors/svgs.100100.mint/proofs
 
{
    method: 'ed25519-v2',
    public: 'WAweF9PHlboQoW0z8NqhZXFmzUTaV74NRFAd/aILprE=',
    digest: '4969e3c012b66d88cec597bf337fc01eab8d651e6ed2d5c40236cc1f7d93435a',
    result: '0G2gvSfBx6MwPT8ShBaiYx7zwa5Kqc4Cq3S3NXV1m5/ZPozoH/SUouuhi9sQU+f0yo0eX4ygH7PzE3PAdlxsCQ==',
    custom: {
        labels: ['preferred'],
        moment: '2023-11-27T17:18:13.034Z'
    }
}

After that, someone else marks the svgs.200200.tesla anchor as preferred:

POST /v2/anchors/svgs.200200.tesla/proofs
 
{
    method: 'ed25519-v2',
    public: 'WAweF9PHlboQoW0z8NqhZXFmzUTaV74NRFAd/aILprE=',
    digest: '4969e3c012b66d88cec597bf337fc01eab8d651e6ed2d5c40236cc1f7d93435a',
    result: '0G2gvSfBx6MwPT8ShBaiYx7zwa5Kqc4Cq3S3NXV1m5/ZPozoH/SUouuhi9sQU+f0yo0eX4ygH7PzE3PAdlxsCQ==',
    custom: {
        labels: ['preferred'],
        moment: '2023-11-27T17:18:14.034Z'
    }
}

Because the labels policy doesn’t allow preferred to be assigned to multiple anchors with the same wallet and symbol, the ledger will actually move the preferred label from svgs.100100.mint to svgs.200200.tesla.

Reassigning labels automatically prevents bugs because of incorrect implementations and makes it simpler to connect to the system because there are fewer operations that external systems need to perform.

This behavior makes the feature easy to use to end users, and makes labels the way to go in complex situations like that. In this example, users don’t have to worry about what is marked as preferred currently, they only need to state what they want as final state. Because of policies, we also get a strict data model, without duplicates and invalid states.

Automatic removal of unique label

Since the initiator appends a proof with labels operation to a record, this proof will correspond to the labels mutation on the same record. On the other hand, the record that looses a label due to unique constraint will have a proof signed by ledger system signer which will instruct the removal of this label. An example of a proof added by the ledger for example above.

// Ledger adds this proof to `svgs.100100.mint` anchor internally
{
    method: 'ed25519-v2',
    public: '<ledger-system-public>',
    digest: '4969e3c012b66d88cec597bf337fc01eab8d651e6ed2d5c40236cc1f7d93435a',
    result: '0G2gvSfBx6MwPT8ShBaiYx7zwa5Kqc4Cq3S3NXV1m5/ZPozoH/SUouuhi9sQU+f0yo0eX4ygH7PzE3PAdlxsCQ==',
    custom: {
        labels: { $pull: 'preferred' },
        detail: 'Reassigning {preferred} label to the record {svgs.200200.tesla}'
        moment: '2023-11-27T17:18:13.034Z'
    }
}

Querying by labels

Labels can also be useful for querying, ledger allows users to query labeled records by using label names:

GET /v2/wallets/tel:1234/anchors?meta.labels=preferred
 
// Returns an array of all anchors labeled as "preferred"
// Always returns an array, even it there is a single match
// because it is a query on a collection

MongoDB Query Language works by doing contains operation on arrays if you pass a single value, see more info here.

Notifying about changed record labels

Changing the labels of the record fires also <record>-updated event where the new state of record labels will be delivered to the listener in the record meta of the payload. Also the last proof which caused the change in labels will be included in this payload.

For the anchor svgs.200200.tesla above when the preferred label is applied the following event payload will be delivered to the listener who registered the effect for anchor-updated

{
  ...,
  data: {
    signal: 'anchor-updated',
    parent: {
      ...,
      data: {
        ...
      },
      meta: {
        labels: [],
        proofs: [ ... ]
      }
    },
    anchor: {
      ...,
      data: {
        ...
      },
      meta: {
        labels: ['preferred'],
        proofs: [ ..., {
           ...,
           custom: {
             labels: ['preferred'],
             moment: '2023-11-27T17:18:14.034Z'
           }
        }]
      }
    }
  }
}

Also for the anchor svgs.100100.mint the anchor-updated event will be delivered where the preferred label is removed:

{
  ...,
  data: {
    signal: "anchor-updated",
    parent: {
      ...,
      data: {
        ...
      },
      meta: {
        labels: ['preferred'],
        proofs: [ ... ]
      }
    },
    anchor: {
      ...,
      data: {
        ...
      },
      meta: {
        labels: [],
        proofs: [ ..., {
           ...,
           custom: {
             labels: { '$pull': 'preferred'},
             moment: '2023-11-27T17:18:14.034Z'
           }
        }]
      }
    }
  }
}

On this page