Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
33 changes: 6 additions & 27 deletions packages/dpns-contract/schema/v1/dpns-contract-documents.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,11 @@
}
},
{
"name": "dashIdentityId",
"name": "identityId",
"nullSearchable": false,
"properties": [
{
"records.dashUniqueIdentityId": "asc"
}
],
"unique": true
},
{
"name": "dashAlias",
"properties": [
{
"records.dashAliasIdentityId": "asc"
"records.identity": "asc"
}
]
}
Expand Down Expand Up @@ -91,32 +83,19 @@
"records": {
"type": "object",
"properties": {
"dashUniqueIdentityId": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"position": 0,
"contentMediaType": "application/x.dash.dpp.identifier",
"description": "Identity ID to be used to create the primary name the Identity",
"$comment": "Must be equal to the document owner"
},
"dashAliasIdentityId": {
"identity": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please call it dashIdentityId it means dash identity identifier. We use always postfix id if it's identifier (ownerId).
Postfix dash make sense because other networks also have identities (yes we are going to resolve addresses for other networks as well) but here we referencing to our dash identities

@QuantumExplorer QuantumExplorer Jul 23, 2024 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave what chat-gpt-4o says on the matter here:

In this debate, both perspectives have merit, but let's break down the arguments based on naming conventions and industry practices:

Naming Conventions and Industry Practices

  1. Contextual Naming vs. Technical Naming:
    • Your Perspective (Contextual Naming): Naming fields based on the business logic and the end-use (e.g., identity instead of identityId) is often more user-friendly and aligns with practices in systems like DNS and ENS. In these systems, the names used are typically user-facing and meant to be intuitive, reflecting the business concept rather than the underlying technical detail.
    • Colleague's Perspective (Technical Naming): Using identityId provides clarity that this field holds an identifier, which can be beneficial for developers and systems interfacing with multiple types of identifiers. It explicitly states that the value is an identifier, which can reduce ambiguity.
  2. Namespace Prefixing:
    • Your Perspective (Non-Prefixing): Avoiding prefixes like DashIdentity aligns with practices in broader industry standards, promoting interoperability and avoiding unnecessary verbosity. This approach also helps in keeping records concise, which can reduce costs and complexity.
    • Colleague's Perspective (Prefixing): Prefixing with Dash might help in scenarios where there's a need to distinguish between different identity services or networks. It can be useful in multi-network environments to avoid confusion, especially if there are overlapping namespaces or similar constructs in different networks.

Common Practices in DNS and ENS

• DNS: In DNS, records are named based on their function and the resource they point to, such as A, AAAA, MX, TXT, etc. These names are intuitive and indicate the type of resource directly, without additional prefixes.
• ENS: Similar to DNS, ENS names records based on their purpose and the resource they represent, like address, contenthash, text, etc. This approach emphasizes ease of use and understanding for end-users and developers alike.

Evaluation

• User-Friendliness: Naming fields in a way that reflects their business purpose (identity) rather than their technical representation (identityId) aligns with practices in user-facing systems and can make the system more intuitive.
• Technical Clarity: Using identityId can provide clear technical context, especially in a multi-service environment, reducing potential ambiguities.

Conclusion

Both perspectives have valid points, but the preference often leans towards user-friendly, business logic-based naming in high-level schemas and interfaces, which aligns with industry standards seen in DNS and ENS. However, for lower-level or more technically detailed interfaces, including the type of data (like Id) can be beneficial for clarity.

Given that DPNS aims to be a user-facing service similar to DNS and ENS, your approach to use identity without the prefix or suffix seems more aligned with making the service intuitive and easy to use. Your colleague's point about being clear with identifiers is valid in a more technical or backend context, where such distinctions are critical. For high-level schemas intended for broader use, keeping names straightforward and business-oriented typically prevails.

"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 32,
"position": 1,
"contentMediaType": "application/x.dash.dpp.identifier",
"description": "Identity ID to be used to create alias names for the Identity",
"$comment": "Must be equal to the document owner"
"description": "Identifier name record that refers to an Identity"
}
},
"minProperties": 1,
"maxProperties": 1,
"position": 5,
"additionalProperties": false,
"$comment": "Constraint with max and min properties ensure that only one identity record is used - either a `dashUniqueIdentityId` or a `dashAliasIdentityId`"
"additionalProperties": false
},
"subdomainRules": {
"type": "object",
Expand Down
52 changes: 4 additions & 48 deletions packages/dpns-contract/test/unit/dpnsContract.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('DPNS Contract', () => {
normalizedParentDomainName: 'dash',
preorderSalt: crypto.randomBytes(32),
records: {
dashUniqueIdentityId: await generateRandomIdentifier(),
identity: await generateRandomIdentifier(),
},
subdomainRules: {
allowSubdomains: false,
Expand Down Expand Up @@ -468,54 +468,10 @@ describe('DPNS Contract', () => {
});

describe('Dash Identity', () => {
it('should have either `dashUniqueIdentityId` or `dashAliasIdentityId`', async () => {
rawDomainDocument.records = {
dashUniqueIdentityId: identityId,
dashAliasIdentityId: identityId,
};

const document = dpp.document.create(dataContract, identityId, 'domain', rawDomainDocument);
const validationResult = document.validate(dpp.protocolVersion);
const error = expectJsonSchemaError(validationResult);

expect(error.keyword)
.to
.equal('maxProperties');
expect(error.instancePath)
.to
.equal('/records');
});

describe('dashUniqueIdentityId', () => {
it('should no less than 32 bytes', async () => {
rawDomainDocument.records = {
dashUniqueIdentityId: crypto.randomBytes(30),
};

expect(() => {
dpp.document.create(dataContract, identityId, 'domain', rawDomainDocument);
})
.to
.throw();
});

it('should no more than 32 bytes', async () => {
rawDomainDocument.records = {
dashUniqueIdentityId: crypto.randomBytes(64),
};

expect(() => {
dpp.document.create(dataContract, identityId, 'domain', rawDomainDocument);
})
.to
.throw();
});
});

describe('dashAliasIdentityId', () => {
describe('identity record', () => {
it('should no less than 32 bytes', async () => {
rawDomainDocument.records = {
dashAliasIdentityId: crypto.randomBytes(30),
identity: crypto.randomBytes(30),
};

expect(() => {
Expand All @@ -527,7 +483,7 @@ describe('DPNS Contract', () => {

it('should no more than 32 bytes', async () => {
rawDomainDocument.records = {
dashAliasIdentityId: crypto.randomBytes(64),
identity: crypto.randomBytes(64),
};

expect(() => {
Expand Down
18 changes: 9 additions & 9 deletions packages/platform-test-suite/test/e2e/dpns.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('DPNS', () => {
// skip if DPNS owner private key is not passed and use `dash` in tests above
it('should be able to register a TLD', async () => {
createdTLD = await ownerClient.platform.names.register(newTopLevelDomain, {
dashAliasIdentityId: identity.getId(),
identity: identity.getId(),
}, identity);

// Additional wait time to mitigate testnet latency
Expand Down Expand Up @@ -148,7 +148,7 @@ describe('DPNS', () => {

try {
await client.platform.names.register(getRandomDomain(), {
dashAliasIdentityId: identity.getId(),
identity: identity.getId(),
}, identity);
} catch (e) {
broadcastError = e;
Expand All @@ -161,7 +161,7 @@ describe('DPNS', () => {

it('should be able to register a second level domain', async () => {
registeredDomain = await client.platform.names.register(`${secondLevelDomain}0.${topLevelDomain}`, {
dashUniqueIdentityId: identity.getId(),
identity: identity.getId(),
}, identity);

// Additional wait time to mitigate testnet latency
Expand All @@ -179,7 +179,7 @@ describe('DPNS', () => {
const domain = `${secondLevelDomain}O.${topLevelDomain}`;

await client.platform.names.register(domain, {
dashAliasIdentityId: identity.getId(),
identity: identity.getId(),
}, identity);

expect.fail('should throw error');
Expand All @@ -199,7 +199,7 @@ describe('DPNS', () => {
const domain = `${getRandomDomain()}.${getRandomDomain()}`;

await client.platform.names.register(domain, {
dashAliasIdentityId: identity.getId(),
identity: identity.getId(),
}, identity);

expect.fail('should throw error');
Expand Down Expand Up @@ -252,8 +252,8 @@ describe('DPNS', () => {

it('should be able to resolve domain by it\'s record', async () => {
const [document] = await client.platform.names.resolveByRecord(
'dashUniqueIdentityId',
registeredDomain.getData().records.dashUniqueIdentityId,
'identity',
registeredDomain.getData().records.identity,
);

const rawDocument = document.toObject();
Expand Down Expand Up @@ -310,9 +310,9 @@ describe('DPNS', () => {
expect(broadcastError.code).to.equal(40500);
});

it('should not be able to register two domains with same `dashAliasIdentityId` record');
it('should not be able to register two domains with same `identity` record');

it('should be able to register many domains with same `dashAliasIdentityId` record');
it('should be able to register many domains with same `identity` record');

it('should not be able to update preorder');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@
"unique": {
"type": "boolean"
},
"nullSearchable": {
"type": "boolean"
},
"contested": {
"type": "object",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use std::convert::TryInto;

#[cfg(feature = "validation")]
use crate::consensus::basic::data_contract::ContestedUniqueIndexOnMutableDocumentTypeError;
#[cfg(feature = "validation")]
use crate::consensus::basic::data_contract::ContestedUniqueIndexWithUniqueIndexError;
#[cfg(any(test, feature = "validation"))]
use crate::consensus::basic::data_contract::InvalidDocumentTypeNameError;
#[cfg(feature = "validation")]
Expand Down Expand Up @@ -50,6 +52,7 @@ use crate::version::PlatformVersion;
use crate::ProtocolError;
use platform_value::btreemap_extensions::BTreeValueMapHelper;
use platform_value::{Identifier, Value};

const NOT_ALLOWED_SYSTEM_PROPERTIES: [&str; 1] = ["$id"];

const SYSTEM_PROPERTIES: [&str; 11] = [
Expand Down Expand Up @@ -282,6 +285,12 @@ impl DocumentTypeV0 {
#[cfg(feature = "validation")]
let mut unique_indices_count = 0;

#[cfg(feature = "validation")]
let mut last_non_contested_unique_index_name: Option<String> = None;

#[cfg(feature = "validation")]
let mut last_contested_unique_index_name: Option<String> = None;

#[cfg(feature = "validation")]
let mut contested_indices_count = 0;

Expand Down Expand Up @@ -330,6 +339,23 @@ impl DocumentTypeV0 {
.into(),
)));
}

if let Some(last_contested_unique_index_name) =
last_contested_unique_index_name.as_ref()
{
return Err(ProtocolError::ConsensusError(Box::new(
ContestedUniqueIndexWithUniqueIndexError::new(
name.to_string(),
last_contested_unique_index_name.clone(),
index.name,
)
.into(),
)));
}

if index.contested_index.is_none() {
last_non_contested_unique_index_name = Some(index.name.clone());
}
}

if index.contested_index.is_some() {
Expand All @@ -355,6 +381,19 @@ impl DocumentTypeV0 {
)));
}

if let Some(last_unique_index_name) =
last_non_contested_unique_index_name.as_ref()
{
return Err(ProtocolError::ConsensusError(Box::new(
ContestedUniqueIndexWithUniqueIndexError::new(
name.to_string(),
index.name,
last_unique_index_name.clone(),
)
.into(),
)));
}

if documents_mutable {
return Err(ProtocolError::ConsensusError(Box::new(
ContestedUniqueIndexOnMutableDocumentTypeError::new(
Expand All @@ -364,6 +403,8 @@ impl DocumentTypeV0 {
.into(),
)));
}

last_contested_unique_index_name = Some(index.name.clone());
}

// Index names must be unique for the document type
Expand Down
12 changes: 12 additions & 0 deletions packages/rs-dpp/src/data_contract/document_type/index/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ pub struct Index {
pub name: String,
pub properties: Vec<IndexProperty>,
pub unique: bool,
/// Null searchable indicates what to do if all members of the index are null
/// If this is set to false then we do not insert references which makes such items non-searchable
pub null_searchable: bool,
/// Contested indexes are useful when a resource is considered valuable
pub contested_index: Option<ContestedIndexInformation>,
}
Expand Down Expand Up @@ -431,6 +434,9 @@ impl TryFrom<&[(Value, Value)]> for Index {
// For properties, we iterate each and move it to IndexProperty

let mut unique = false;
// The default for null searchable should be true. Do not change this without very
// careful thought and consideration.
let mut null_searchable = true;
let mut name = None;
let mut contested_index = None;
let mut index_properties: Vec<IndexProperty> = Vec::new();
Expand All @@ -454,6 +460,11 @@ impl TryFrom<&[(Value, Value)]> for Index {
unique = value_value.as_bool().expect("confirmed as bool");
}
}
"nullSearchable" => {
if value_value.is_bool() {
null_searchable = value_value.as_bool().expect("confirmed as bool");
}
}
"contested" => {
let contested_properties_value_map = value_value.to_map()?;

Expand Down Expand Up @@ -580,6 +591,7 @@ impl TryFrom<&[(Value, Value)]> for Index {
name,
properties: index_properties,
unique,
null_searchable,
contested_index,
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl Index {
name: index_name,
properties,
unique,
null_searchable: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to have it default you better give it opposite name so default value will be false (which is convention) and you use true to override it (to ignore nulls)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the opposite is very hard to understand: "ignore_null_searchable"? This is already hard enough.

contested_index: None,
})
}
Expand Down
Loading