Representing Mina's "vesting" accounts using balance exemptions

Hi Rosetta team,

We’re working on correctly representing Mina’s notion of time-locked accounts for its Rosetta integration and were hoping you could provide us with some feedback.

Certain accounts in Rosetta are configured with a vesting schedule that enforces that some subset of tokens in an account are initially illiquid, with new chunks becoming liquid at fixed time intervals. The intervals can be arbitrarily short, so every such account can see its liquid balance change in every single block.

Returning only the liquid subset in calls to /accounts/balance is straightforward, but we don’t have corresponding operations representing these balance changes. For a few reasons, it’s impossible for us to directly create transactions/operations for the balance changes.

We’ve reviewed Celo’s implementation of vesting using their “ReleaseGold” smart contracts which are represented as sub-accounts in Rosetta, but unfortunately we can’t implement something equivalent in Mina.

So the appropriate solution for Mina appears to be to mark accounts with vesting schedules as balance-exempt and implementing historical balance lookups for all accounts.

One concern we have there is that we’re not sure whether the list of balance-exempt account identifiers we return in /network/options can change as new vesting accounts are created (and perhaps removed once past the end of their vesting timeline). Is returning a different balance_exemptions array across calls currently allowed? If not is it possible to update the Rosetta specification to support that in some way?

Thanks!

An RFC on Mina’s repo that goes into implementation details for our protocol is shared here https://github.com/MinaProtocol/mina/issues/6931

:wave: @omerzach…sorry for the delay!

Thanks for the detailed write-up on the situation! Helps a lot in considering possible solutions.

Great question! The /network/options response is considered to be static during a single client instantiation. In fact, we initialize our clients with a cached version of /network/options to ensure we are very intentional about all changes (we want to error if we see a new operation type we didn’t expect, for example). TL;DR no, it is not recommended to return different balance_exemptions on each call.

All this being said, there has been interest from a few other to allow for more granular/dynamic balance exemption identification. In previous discussions, @alan.verbner proposed adding a regex filter to exemptions if exempt accounts could be uniquely identified by the form of their address (although this doesn’t seem to work for your case). The other alternative would be to add an optional exemption_type to the AccountIdentifier. This would allow you to specify that a particular account is subject to a certain exemption. The only unresolved issue I can think of is a little weirdness related to querying balance exemption accounts (it is weird that the field would exist there). Would something like this work? If we added this field to AccountIdentifier, we could probably deprecate the balance_exemptions response in /network/options as well (just preferring the exemption is attached to the AccountIdentifier it affects).

An option I like less would be to add a exemption_type field to the Operation model (allowing the implementer to indicate that there were previous changes that weren’t accounted for) but I think this makes less sense/seems confusing.

Do you have any other ideas that would work better for your use case? Maybe something that can better expose the semantics of the vesting?

Hey @Patrick.ogrady,

Thanks for getting back to us! I think adding an exemption_type field to AccountIdentifier would work perfectly.

I don’t understand what would be weird about querying balance exempt accounts though; do you think you could clarify what you mean?

Thanks!

:+1:

We use AccountIdentifier as a unique identifier for a given Account (hash(AccountIdentifier)identifies an account, including metadata and SubAccountIdentifier). If we introduce a new field into this model, we will break this model as the caller may not know if an account is subject to an exemption when querying/this exemption could disappear at a later date. I’m wondering if it makes more sense to add an /account/info endpoint that can be used to check info like this?

Quick follow-up…are exemptions temporal?

Ah I see. I wasn’t thinking of the AccountIdentifier s as needing to be static beyond the address and SubAccountIdentifier . An /account/info endpoint which returns dynamic settings makes perfect sense to me.

Balance exemptions are somewhat temporal in the sense that an account can reach its final “vested” status and then no longer have any new balance change that isn’t reflected in an operation, but the account would still be missing operations historically, so from Rosetta’s perspective I think any Mina account that was ever vesting will need to be considered balance-exempt forever.

I think something like this could also return known SubAccountIdentifiers or “address type” (i.e. P2SH or P2WSH).

Ahhh that’s a good point. For some reason, I had the notion in my head that an account could become un-exempted.

Hi @patrick.ogrady, I’m LanfordCai in Github.

The response of EOS’s /v1/chain/get_account endpoint looks like this:

{
    "account_name": "b1",
    "head_block_num": 157181858,
    "head_block_time": "2020-12-12T02:15:34.500",
    "privileged": false,
    "last_code_update": "2019-05-15T08:32:56.000",
    "created": "2018-06-10T13:30:30.500",
    "core_liquid_balance": "71052.5042 EOS"
}

We can see there are a lot information, both static(account_name, created) and dynamic(core_liquid_balance, last_code_update).

AccountIdentifier is no need to be static, how about add a field named additional_info to store the addtional informations about an account, like format and balance_exempt ? This field shouldn’t be count in when calculate the hash of AccountIdentifier . (The format of an address is static, but I think it’s ok to put it into addtional_info.

Then, add a new endpoint named /account/info(or /account ?), by calling this, the AccountIdentifier with additional_info would be return. It looks like this:

/account/info

Request

{
    "network_identifier": {
        "blockchain": "rosetta",
        "network": "mainnet"
    },
    "account_identifier": {
        "address": "0x3a065000ab4183c6bf581dc1e55a605455fc6d61",
        "metadata": {}
    },
    "block_identifier": {
        "index": 1123941,
        "hash": "0x1f2cc6c5027d2f201a5453ad1119574d2aed23a392654742ac3c78783c071f85"
    }
}

Response

{
    "block_identifier": {
        "index": 1123941,
        "hash": "0x1f2cc6c5027d2f201a5453ad1119574d2aed23a392654742ac3c78783c071f85"
    },
    "account_identifier": {
        "address": "0x3a065000ab4183c6bf581dc1e55a605455fc6d61",
        "metadata": {},
        "addtional_info": {
            "last_code_update": "2019-05-15T08:32:56.000",
            "format": "Normal",
            "balance_exempt": [
                {
                    "value": "1238089899992",
                    "currency": {
                        "symbol": "ROS",
                        "decimals": 8
                    },
                    "metadata": {}
                }
            ]
        }
    }
    "metadata": {}
}

If you don’t want additional_info field in AccountIdentifier, or you think it’s confusing about when it should be presented, we don’t add this field and just return the account_info when /account/info be called, the response looks like this:

{
    "block_identifier": {
        "index": 1123941,
        "hash": "0x1f2cc6c5027d2f201a5453ad1119574d2aed23a392654742ac3c78783c071f85"
    },
    "account_identifier": {
        "address": "0x3a065000ab4183c6bf581dc1e55a605455fc6d61",
        "metadata": {}
    },
    "account_info": {
        "last_code_update": "2019-05-15T08:32:56.000",
        "format": "Normal",
        "balance_exempt": [
            {
                "value": "1238089899992",
                "currency": {
                    "symbol": "ROS",
                    "decimals": 8
                },
                "metadata": {}
            }
        ]
    },
    "metadata": {}
}