Property Schemas

Each entry of a document type's properties is a property schema: JSON Schema (draft 2020-12), limited to the keywords in this chapter, plus three Platform keywords that say how a value is stored (position, byteArray and contentMediaType). The schema is checked when the contract is registered, and every created or replaced document is validated against it. Platform keywords with more to them, such as maxBytes, refersTo, distinctFrom, encryptedFor, generatedFrom, requiredSince and a typed array's items, have chapters of their own.

KeywordApplies toOn update
typeevery propertyFixed
positionevery propertyFixed
minLength, maxLength, pattern, formatstringsLoosened or removed only
minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOfintegers and numbersLoosened or removed only, keeping an integer's width; multipleOf fixed
enum, constanyenum may gain values; const may be removed
byteArray, contentMediaTypebyte arraysFixed
"identifier", "bytes" and sizeidentifiers and fixed-size byte arraysSwitching to or from the long form is no change; otherwise as the long form
minItems, maxItems, uniqueItems, containsarraysLoosened or removed only; contains fixed
properties, required, additionalProperties, minProperties, maxProperties, dependentRequiredobjectsMembers may be added; the rest fixed, except dependentRequired may lose entries
$refanyFixed
$id, $comment, description, examplesanyFree ($id may only be added)

Each section gives the error a contract update gets for breaking its rules. Most are refused with IncompatibleDocumentTypeSchemaError (10246), from the comparison of the old and new schemas; a change to how a value is stored is refused with DocumentTypeUpdateError (40212).

How property schemas are checked

When a contract is registered or updated:

  • From protocol version 14, a property written with a shorthand is read as the long form it stands for. Every check below sees the long form; the contract is stored as it was sent.
  • The schema is checked against the document meta-schema. A keyword the meta-schema does not allow where it is written, or a value of the wrong shape, is refused with JsonSchemaError (10101).
  • The parser reads each property's type and bounds, and refuses what the meta-schema cannot express (InvalidContractStructure, 10231).
  • The schema is compiled for validating documents. A pattern that is not a valid regular expression, or a format the validator does not know, is refused here (JsonSchemaError, 10101).

When a document is created or replaced:

  1. Every string and byte array value is at most 5120 bytes, whatever the schema allows (DocumentFieldMaxSizeExceededError, 10417). From protocol version 14 a value nested more than 256 levels deep is refused as well (ValueError, 10103).
  2. The document's properties are validated against the schema. Each failure is reported as a JsonSchemaError (10101) naming the keyword and the property.
  3. Platform's own checks, which JSON Schema cannot express, come next: maxBytes and propertyConstraints among them.

A transfer, a price update and a purchase carry no property values, so they are not validated against the schema again.

The schema also decides how each value is stored, since a stored document holds no property names or type tags:

PropertyStored as
integer1, 2, 4 or 8 bytes, chosen by its bounds (see Numbers)
number8 bytes, a 64-bit floating point number
boolean1 byte
stringa length prefix, then the UTF-8 bytes
byte array with minItems equal to maxItemsthe bytes, with no prefix
any other byte arraya length prefix, then the bytes
identifier32 bytes
objecta length prefix, then its members
typed arrayan element count, then the elements (see Typed Arrays)

An optional property adds one byte in front that says whether it is present. See Document Serialization for the exact encoding.

type

WhereEvery property; also the elements of a typed array
ValueOne of "string", "integer", "number", "boolean", "object", "array"; from protocol version 14 also the shorthands "identifier" and "bytes"
Sinceprotocol version 1
On updateFixed (IncompatibleDocumentTypeSchemaError, 10246), except that a shorthand and its long form are the same type
ErrorsJsonSchemaError (10101): a document value of another type

type is a single name. A list of types, and "null", are refused at registration: every stored value needs one known encoding.

An array is one of two things:

  • a byte array, with byteArray: true: a string of bytes, such as a hash or an identifier;
  • from protocol version 14, a typed array, with an items schema: a list of values of one scalar type. See Typed Arrays.

An array that is neither is refused.

position

WhereEvery property, at every level. Not on the elements of a typed array.
ValueAn integer, 0 or more
Sinceprotocol version 1
On updateFixed (10246)
ErrorsMissingPositionsInDocumentTypePropertiesError (10411) at registration

A document is stored with its values one after another and no property names. position is the property's place in that sequence.

  • The top-level properties of a document type must use the positions 0, 1, 2 and so on, with no gap and no number used twice (10411). A top-level property without a position is refused.
  • The members of an object need a position too. Number them from 0 within the object; only the top level is checked for gaps.
  • A property that takes its schema from a $ref writes its position next to the $ref.
  • A property added by a contract update takes the next free position. An existing position can never change, or stored documents would be read in the wrong order.

Strings

KeywordsminLength, maxLength, pattern, format
WhereProperties of type string, and the string elements of a typed array
ValueminLength, maxLength: an integer, 0 or more, counting characters. pattern: a regular expression. format: the name of a JSON Schema format, such as "uri"
Sinceprotocol version 1
On updatemaxLength may be raised or removed, and minLength lowered or removed. pattern and format may be removed. None of them may be added, and no other change is allowed (10246).
ErrorsJsonSchemaError (10101)
"username": {
  "type": "string",
  "minLength": 3,
  "maxLength": 63,
  "pattern": "^[a-zA-Z0-9_]+$",
  "position": 0
}

A username is 3 to 63 letters, digits or underscores.

  • minLength and maxLength count characters, and a character takes 1 to 4 bytes in UTF-8. To cap the stored size, add maxBytes. Whatever maxLength says, no single string may exceed 5120 bytes (10417).
  • pattern is written in the syntax of Rust's regex crate, which has no lookaround and no backreferences. A pattern that does not compile is refused at registration (10101).
  • format is checked on every document. The validator knows date-time, date, time, email, idn-email, hostname, ipv4, ipv6, uri and regex. Any other format, uuid and uri-reference included, is refused at registration (10101).
  • A string with pattern or format must declare a maxLength of at most 50000, so that matching stays cheap.
  • A string used in an index needs a maxLength of at most 63 (InvalidIndexedPropertyConstraintError, 10205). See Indexes.

Numbers

Keywordsminimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
WhereProperties of type integer or number, and such elements of a typed array
ValueA number. On an integer element of a typed array, minimum and maximum are integers.
Sinceprotocol version 1
On updatemaximum and exclusiveMaximum may be raised or removed, and minimum and exclusiveMinimum lowered or removed, unless that changes how an integer is stored (DocumentTypeUpdateError, 40212). None may be added, and multipleOf is fixed (10246).
ErrorsJsonSchemaError (10101)
"rating": { "type": "integer", "minimum": 1, "maximum": 5, "position": 2 }

A rating is a whole number from 1 to 5, and is stored in one byte.

A number is always stored in 8 bytes. An integer is stored in the smallest width its minimum and maximum allow, when the contract's config has sizedIntegerTypes on (the default; see Contract-Level Keys and config):

minimummaximumStored as
0 or moreup to 2551 byte, unsigned
0 or moreup to 655352 bytes, unsigned
0 or moreup to 42949672954 bytes, unsigned
0 or morehigher8 bytes, unsigned
below 0both bounds within -128 to 1271 byte, signed
below 0both bounds within -32768 to 327672 bytes, signed
below 0both bounds within -2147483648 to 21474836474 bytes, signed
below 0otherwise8 bytes, signed

With only a minimum, the integer takes 8 bytes, unsigned when the minimum is 0 or more. With only a maximum, it takes the unsigned width the maximum gives, so an integer that may be negative needs a minimum too. With neither, an enum of integers picks the width from its smallest and largest members; without one, the integer takes 8 bytes, signed. exclusiveMinimum and exclusiveMaximum do not affect the width. With sizedIntegerTypes off, every integer takes 8 bytes, signed.

Stored documents and index entries hold each integer at its width, so a contract update may not change the width or the sign. Raising maximum past the width, lowering minimum below 0, removing a bound, or adding an enum value outside the width is refused with DocumentTypeUpdateError (40212); so is turning sizedIntegerTypes on when it would change an existing integer's width. A change that keeps the width is accepted.

In an index, an integer property is keyed so that the keys sort as the numbers do: an unsigned integer by its big-endian bytes, and a signed one by its big-endian bytes with the sign bit flipped, which puts the negative values first. Before protocol version 14 an unsigned integer was keyed with its top bit flipped as well, so the values from the middle of its range up (128 and above, for one byte) sorted below the others in range queries, ordering, counts and sums. The first block of protocol version 14 moves the keys stored that way to their new keys. Three kinds of keys keep the earlier encoding: the values of a contested index in the paths of its vote poll, which the masternodes' votes point at, so the contested values of an unsigned property are listed in that order; the system timestamps and heights, whose values never reach the flipped bit; and the values of an unsigned property that ends a rangeSummable index, in every index of the document type. That index keeps a sum in every node of the tree holding those values, and values of both signs whose sums fit in the earlier order can leave the signed 64-bit range in every balanced tree of the new order (an update before protocol version 14 can make a summed property unsigned while it holds negative values, so its type does not rule them out), so the property is keyed the earlier way at every protocol version, and its order stays as it was.

enum and const

WhereAny property. enum also on the string, integer, number and boolean elements of a typed array; const never on elements.
Valueenum: an array of one or more values, none repeated. const: one value.
Sinceprotocol version 1
On updateenum may gain values but not lose one; the keyword may be removed, not added. const may be removed, not added or changed (10246). An enum value that changes an integer's width is refused (DocumentTypeUpdateError, 40212).
ErrorsJsonSchemaError (10101)
"status": { "type": "string", "enum": ["open", "closed", "archived"], "position": 3 }

enum lists the values a property may take and const the one value it must take. Both only narrow what documents may hold, so an update may widen them (more enum values, or no keyword at all) but never narrow them, which could leave stored documents invalid. On an integer without bounds, enum also sets the stored width (see Numbers).

Byte arrays and identifiers

KeywordsbyteArray, contentMediaType
WhereProperties of type array, and the array elements of a typed array
ValuebyteArray: true, the only value. contentMediaType: "application/x.dash.dpp.identifier" makes the byte array an identifier.
Sinceprotocol version 1
On updateFixed (10246). The length bounds follow the rules of Arrays, but a byte array may not switch between a fixed and a variable length, or change its fixed length (DocumentTypeUpdateError, 40212).
ErrorsJsonSchemaError (10101)
"authorId": {
  "type": "array",
  "byteArray": true,
  "minItems": 32,
  "maxItems": 32,
  "contentMediaType": "application/x.dash.dpp.identifier",
  "position": 1
}

authorId is an identifier: the 32-byte id of an identity, a document, a contract or a token.

  • byteArray: true makes an array a string of bytes. Its minItems and maxItems count bytes. When the two are equal the bytes are stored as they are; otherwise they carry a length prefix.
  • contentMediaType: "application/x.dash.dpp.identifier" makes a byte array an identifier. It must come with byteArray: true, minItems: 32 and maxItems: 32, and it may not carry uniqueItems. An identifier is shown in base58, and it is the kind of property distinctFrom and most refersTo targets are declared on.
  • A byte array used in an index needs a maxItems of at most 255 (InvalidIndexedPropertyConstraintError, 10205).
  • On a typed array, contentMediaType belongs on the items, not on the array.
  • From protocol version 14, an identifier and a byte array of fixed length can each be written in one keyword; see Shorthands.

Shorthands: identifier and bytes

Keywords"type": "identifier"; "type": "bytes" with size
WhereEvery property, at every level, the elements of a typed array, and the definitions in schemaDefs
Valuesize: an integer from 1 to 5120
Sinceprotocol version 14
On updateRewriting a property or a definition from the shorthand to its long form, or back, is no change. Any other change follows the rules of the long form.
ErrorsInvalidContractStructure (10231) at registration; JsonSchemaError (10101) before protocol version 14

Each shorthand stands for a byte array written in full:

ShorthandStands for
"type": "identifier""type": "array", "byteArray": true, "minItems": 32, "maxItems": 32, "contentMediaType": "application/x.dash.dpp.identifier"
"type": "bytes", "size": n"type": "array", "byteArray": true, "minItems": n, "maxItems": n

Written in full, a payment's recipient and the hash of the transaction that paid it are:

"recipientId": {
  "type": "array",
  "byteArray": true,
  "minItems": 32,
  "maxItems": 32,
  "contentMediaType": "application/x.dash.dpp.identifier",
  "position": 1
},
"txHash": {
  "type": "array",
  "byteArray": true,
  "minItems": 32,
  "maxItems": 32,
  "position": 2
}

With the shorthands they are:

"recipientId": { "type": "identifier", "position": 1 },
"txHash": { "type": "bytes", "size": 32, "position": 2 }

The two spellings are the same property: a value is validated, indexed and stored exactly as under the long form, in the same bytes.

  • The contract is stored, and read back, exactly as it was sent. The platform reads a shorthand as its long form wherever it reads a schema: the meta-schema check, the parser, the schema documents are validated against, and the comparison a contract update is judged by.
  • A shorthand takes every keyword its long form takes: position, a refersTo or distinctFrom on an identifier, an encryptedFor on bytes, and the rest. It may be a member of an object, the items of a typed array, or a definition a $ref names.
  • A shorthand does not repeat what it says. byteArray, minItems, maxItems or contentMediaType beside it is refused (10231), and so is size on an identifier, which is always 32 bytes. bytes needs a size (10231); a byte array of variable length is written in full.
  • size is at least 1 and at most 5120, the most bytes a document field may hold (max_field_value_size), so that the property can be filled (10231). A byte array used in an index needs a size of at most 255 (InvalidIndexedPropertyConstraintError, 10205).
  • An error about a property written with a shorthand names the long form, since that is what was checked: minItems and maxItems stand for size, and "type": "array", byteArray and contentMediaType for identifier. An update changing a size from 32 to 20, for example, is refused for changing the property's minItems and maxItems (DocumentTypeUpdateError, 40212), and one turning an identifier into bytes at /properties/<name>/contentMediaType (10246).
  • The fee for validating the schema at registration is charged on the long form, as for a contract written in full. The contract's storage is charged on the bytes sent.
  • Before protocol version 14 neither identifier nor bytes is a type the meta-schema knows, and a contract writing one is refused (10101).

Arrays

KeywordsminItems, maxItems, uniqueItems, contains
WhereProperties of type array. minItems and maxItems also on byte array elements of a typed array.
ValueminItems, maxItems: an integer, 0 or more. uniqueItems: a boolean. contains: a schema.
Sinceprotocol version 1
On updatemaxItems may be raised or removed and minItems lowered or removed (10246), within the byte array rule above (40212); a typed array keeps its maxItems. uniqueItems may be removed or set to false, not added (10246). contains is fixed (10246).
ErrorsJsonSchemaError (10101)
  • minItems and maxItems count bytes on a byte array and elements on a typed array. A typed array must declare maxItems, at most 1024.
  • uniqueItems: true on a typed array refuses a document that repeats an element. On a plain byte array it refuses a repeated byte. It is refused on an identifier, and on the elements of a typed array.
  • contains is the JSON Schema keyword: at least one element must match the schema it holds.

Objects

Keywordsproperties, required, additionalProperties, minProperties, maxProperties, dependentRequired
WhereProperties of type object
ValueThe same as at the top of a document type: see Document Shape
Sinceprotocol version 1
On updateMembers may be added, never removed; required and additionalProperties are fixed; dependentRequired may lose entries, not gain them (10246). minProperties and maxProperties are fixed (10246).
ErrorsJsonSchemaError (10101)
"records": {
  "type": "object",
  "properties": {
    "identity": { "type": "identifier", "position": 0 }
  },
  "minProperties": 1,
  "additionalProperties": false,
  "position": 5
}

An object groups members under one property, as DPNS groups a name's records.

  • An object declares properties, 1 to 100 members each with a position, and additionalProperties: false, unless it takes its schema from a $ref.
  • Its required names the members every value of the object must hold. It cannot change after the type exists, so a member added by an update is optional.
  • An object is stored with its members inline. An object cannot be indexed, but a member can: an index names it by its dotted path, such as records.identity.

$ref

WhereAny property, except the elements of a typed array
Value"#/$defs/<name>": a definition in the contract's schemaDefs
Sinceprotocol version 1
On updateFixed (10246)
ErrorsInvalidJsonSchemaRefError (10207) at registration

$ref lets several document types share one schema, written once in the contract's schemaDefs:

"schemaDefs": {
  "address": {
    "type": "object",
    "properties": {
      "street": { "type": "string", "maxLength": 100, "position": 0 },
      "city": { "type": "string", "maxLength": 50, "position": 1 }
    },
    "required": ["street", "city"],
    "additionalProperties": false
  }
}

A property of any document type of the contract then reads "shippingAddress": { "$ref": "#/$defs/address", "position": 2 }.

  • Only local references, starting with #, are allowed. The platform places schemaDefs under $defs in every document type (see $schema and $defs).
  • A reference that does not resolve, or that leads back to itself, is refused (10207).
  • The property keeps its own position; the definition supplies everything else.
  • A definition may be written with a shorthand: "schemaDefs": { "owner": { "type": "identifier" } }.
  • The $ref itself cannot change on update. The definition it points at can, under the rules of the keywords it holds (IncompatibleDataContractSchemaError, 10213).

Annotations

Keywords$id, $comment, description, examples
WhereAny property. $comment and description also on the elements of a typed array.
Value$id: a string starting with #. $comment, description: a string. examples: an array of values.
Sinceprotocol version 1
On update$comment, description and examples: free. $id: may be added, not removed or changed (10246).
Errorsnone

Notes for people and tools reading the contract. They do not change what a document may hold.

See also