Skip to content

feat(platform)!: identity nonce for Data Contract Create #1724

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 29, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/js-dash-sdk/src/SDK/Client/Client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,8 @@ describe('Dash - Client', function suite() {
expect(await interceptedSt.verifySignature(
identityFixture.getPublicKeyById(2),
)).to.be.equal(true);
expect(interceptedSt.getEntropy()).to.be.deep.equal(dataContractFixture.getEntropy());
expect(interceptedSt.getIdentityNonce()).to.be
.deep.equal(dataContractFixture.getIdentityNonce());
expect(interceptedSt.getDataContract().toObject())
.to.be.deep.equal(dataContractFixture.toObject());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ describe('Dash - NonceManager', () => {
expect(await nonceManager.getIdentityNonce(identityId)).to.be.equal(2);
clock.restore();
});

it('should bump identity nonce', async () => {
dapiClientMock.platform.getIdentityNonce.resolves({ identityNonce: 1 });
const prevNonce = await nonceManager.getIdentityNonce(identityId);
const nextNonce = await nonceManager.bumpIdentityNonce(identityId);
const currentNonce = await nonceManager.getIdentityNonce(identityId);
expect(nextNonce)
.to.equal(currentNonce)
.to.equal(prevNonce + 1);
});
});

describe('Identity contract nonce', () => {
Expand Down Expand Up @@ -73,5 +83,15 @@ describe('Dash - NonceManager', () => {
.to.be.equal(2);
clock.restore();
});

it('should bump identity contract nonce', async () => {
dapiClientMock.platform.getIdentityContractNonce.resolves({ identityContractNonce: 1 });
const prevNonce = await nonceManager.getIdentityContractNonce(identityId, contractId);
const nextNonce = await nonceManager.bumpIdentityContractNonce(identityId, contractId);
const currentNonce = await nonceManager.getIdentityContractNonce(identityId, contractId);
expect(nextNonce)
.to.equal(currentNonce)
.to.equal(prevNonce + 1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ class NonceManager {
return nonceState.value;
}

public async bumpIdentityNonce(identityId: Identifier): Promise<number> {
const nextIdentityNonce = await this.getIdentityNonce(identityId) + 1;
this.setIdentityNonce(identityId, nextIdentityNonce);
return nextIdentityNonce;
}

public setIdentityContractNonce(identityId: Identifier, contractId: Identifier, nonce: number) {
const identityIdStr = identityId.toString();
const contractIdStr = contractId.toString();
Expand Down Expand Up @@ -141,6 +147,16 @@ class NonceManager {

return nonceState.value;
}

public async bumpIdentityContractNonce(
identityId: Identifier,
contractId: Identifier,
): Promise<number> {
const nextIdentityContractNonce = await this
.getIdentityContractNonce(identityId, contractId) + 1;
this.setIdentityContractNonce(identityId, contractId, nextIdentityContractNonce);
return nextIdentityContractNonce;
}
}

export default NonceManager;
2 changes: 1 addition & 1 deletion packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class Platform {

protected fetcher: Fetcher;

protected nonceManager: NonceManager;
public nonceManager: NonceManager;

/**
* Construct some instance of Platform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ export async function create(

await this.initialize();

const dataContract = this.dpp.dataContract.create(identity.getId(), contractDefinitions);
const identityNonce = await this.nonceManager.getIdentityNonce(identity.getId()) + 1;
const dataContract = this.dpp.dataContract.create(
identity.getId(),
BigInt(identityNonce),
contractDefinitions,
);

this.logger.debug(`[Contracts#create] created data contract "${dataContract.getId()}"`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@ export default async function update(
const dataContractId = dataContract.getId();

const identityContractNonce = await this.nonceManager
.getIdentityContractNonce(identityId, dataContractId) + 1;
.bumpIdentityContractNonce(identityId, dataContractId);

const dataContractUpdateTransition = dpp.dataContract
.createDataContractUpdateTransition(updatedDataContract, BigInt(identityContractNonce));

this.logger.silly(`[DataContract#update] Created data contract update transition ${dataContract.getId()}`);

await signStateTransition(this, dataContractUpdateTransition, identity, 2);
this.nonceManager.setIdentityContractNonce(identityId, dataContractId, identityContractNonce);
// Broadcast state transition also wait for the result to be obtained
await broadcastStateTransition(this, dataContractUpdateTransition);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default async function broadcast(
}

const identityContractNonce = await this.nonceManager
.getIdentityContractNonce(identityId, dataContractId) + 1;
.bumpIdentityContractNonce(identityId, dataContractId);

const documentsBatchTransition = dpp.document.createStateTransition(documents, {
[identityId.toString()]: {
Expand All @@ -55,7 +55,6 @@ export default async function broadcast(

await signStateTransition(this, documentsBatchTransition, identity, 1);

this.nonceManager.setIdentityContractNonce(identityId, dataContractId, identityContractNonce);
// Broadcast state transition also wait for the result to be obtained
await broadcastStateTransition(this, documentsBatchTransition);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function creditTransfer(

recipientId = Identifier.from(recipientId);

const identityNonce = await this.nonceManager.getIdentityNonce(identity.getId()) + 1;
const identityNonce = await this.nonceManager.bumpIdentityNonce(identity.getId());

const identityCreditTransferTransition = dpp.identity
.createIdentityCreditTransferTransition(
Expand All @@ -32,7 +32,6 @@ export async function creditTransfer(

await signStateTransition(this, identityCreditTransferTransition, identity, signerKeyIndex);

this.nonceManager.setIdentityNonce(identity.getId(), identityNonce);
// Skipping validation because it's already done above
await broadcastStateTransition(this, identityCreditTransferTransition, {
skipValidation: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ export async function creditWithdrawal(

const coreFeePerByte = nearestGreaterFibonacci(minRelayFeePerByte);

const identityNonce = await this.nonceManager
.getIdentityNonce(identity.getId()) + 1;
const identityNonce = await this.nonceManager.bumpIdentityNonce(identity.getId());

const identityCreditWithdrawalTransition = dpp.identity
.createIdentityCreditWithdrawalTransition(
Expand All @@ -103,7 +102,6 @@ export async function creditWithdrawal(
options.signingKeyIndex,
);

this.nonceManager.setIdentityNonce(identity.getId(), identityNonce);
// Skipping validation because it's already done above
const stateTransitionResult = await broadcastStateTransition(
this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ export async function update(

const { dpp } = this;

const identityNonce = await this.nonceManager
.getIdentityNonce(identity.getId()) + 1;
const identityNonce = await this.nonceManager.bumpIdentityNonce(identity.getId());

const identityUpdateTransition = dpp.identity.createIdentityUpdateTransition(
identity,
Expand Down Expand Up @@ -94,7 +93,6 @@ export async function update(
// }
this.logger.silly('[Identity#update] Validated IdentityUpdateTransition');

this.nonceManager.setIdentityNonce(identity.getId(), identityNonce);
// Skipping validation because it's already done above
await broadcastStateTransition(this, identityUpdateTransition, {
skipValidation: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ const {
let randomOwnerId = null;

/**
*
* @param {number} identityNonce
* @param {Identifier} [ownerId]
* @return {Promise<DataContract>}
*/
module.exports = async function getDataContractFixture(
identityNonce,
ownerId = randomOwnerId,
) {
const { DataContractFactory, Identifier, getLatestProtocolVersion } = await Platform
Expand Down Expand Up @@ -150,5 +151,5 @@ module.exports = async function getDataContractFixture(
documentsMutableContractDefault: true,
};

return factory.create(ownerId, documents, config);
return factory.create(ownerId, BigInt(identityNonce), documents, config);
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ describe('Platform', () => {
let identity;

before(async () => {
dataContractFixture = await getDataContractFixture();
client = await createClientWithFundedWallet(35000000);

// Looks like updating the contact and keeping history requires about
// 7 million credits in fees. Investigate this further.
identity = await client.platform.identities.register(30000000);
const nextNonce = await client.platform
.nonceManager.bumpIdentityNonce(identity.getId());
dataContractFixture = await getDataContractFixture(nextNonce);
});

after(async () => {
Expand All @@ -42,7 +44,7 @@ describe('Platform', () => {
it('should fail to create new data contract with unknown owner', async () => {
// if no identity is specified
// random is generated within the function
dataContractFixture = await getDataContractFixture();
dataContractFixture = await getDataContractFixture(1);

let broadcastError;

Expand All @@ -61,7 +63,9 @@ describe('Platform', () => {
// Additional wait time to mitigate testnet latency
await waitForSTPropagated();

dataContractFixture = await getDataContractFixture(identity.getId());
const identityNonce = await client.platform.nonceManager
.bumpIdentityNonce(identity.getId());
dataContractFixture = await getDataContractFixture(identityNonce, identity.getId());

await client.platform.contracts.publish(dataContractFixture, identity);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ describe('Platform', () => {
// Additional wait time to mitigate testnet latency
await waitForSTPropagated();

dataContractFixture = await getDataContractFixture(identity.getId());
const identityNonce = await client.platform
.nonceManager.bumpIdentityNonce(identity.getId());
dataContractFixture = await getDataContractFixture(identityNonce, identity.getId());

await client.platform.contracts.publish(dataContractFixture, identity);

Expand All @@ -50,7 +52,9 @@ describe('Platform', () => {
});

beforeEach(async () => {
dataContractFixture = await getDataContractFixture(identity.getId());
const identityNonce = await client.platform
.nonceManager.bumpIdentityNonce(identity.getId());
dataContractFixture = await getDataContractFixture(identityNonce, identity.getId());
});

after(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,9 @@ describe('Platform', () => {
let dataContractFixture;

before(async () => {
dataContractFixture = await getDataContractFixture(identity.getId());
const nextNonce = await client.platform.nonceManager
.bumpIdentityNonce(identity.getId());
dataContractFixture = await getDataContractFixture(nextNonce, identity.getId());

await client.platform.contracts.publish(dataContractFixture, identity);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod property_names {
pub const DATA_CONTRACT: &str = "dataContract";
pub const ENTROPY: &str = "entropy";
pub const IDENTITY_NONCE: &str = "identityNonce";
}
44 changes: 21 additions & 23 deletions packages/rs-dpp/src/data_contract/created_data_contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod v0;
use crate::data_contract::created_data_contract::v0::{
CreatedDataContractInSerializationFormatV0, CreatedDataContractV0,
};
use crate::prelude::DataContract;
use crate::prelude::{DataContract, IdentityNonce};
use crate::version::PlatformVersion;
use crate::ProtocolError;
use bincode::{Decode, Encode};
Expand Down Expand Up @@ -49,7 +49,7 @@ impl PlatformSerializableWithPlatformVersion for CreatedDataContract {
self,
platform_version: &PlatformVersion,
) -> Result<Vec<u8>, ProtocolError> {
let (data_contract, entropy) = self.data_contract_and_entropy_owned();
let (data_contract, identity_nonce) = self.data_contract_and_identity_nonce();
let data_contract_serialization_format: DataContractInSerializationFormat =
data_contract.try_into_platform_versioned(platform_version)?;
let created_data_contract_in_serialization_format = match platform_version
Expand All @@ -60,7 +60,7 @@ impl PlatformSerializableWithPlatformVersion for CreatedDataContract {
0 => Ok(CreatedDataContractInSerializationFormat::V0(
CreatedDataContractInSerializationFormatV0 {
data_contract: data_contract_serialization_format,
entropy_used: entropy,
identity_nonce,
},
)),
version => Err(ProtocolError::UnknownVersionMismatch {
Expand Down Expand Up @@ -99,8 +99,8 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Cre
))
})?
.0;
let (data_contract_in_serialization_format, entropy) =
created_data_contract_in_serialization_format.data_contract_and_entropy_owned();
let (data_contract_in_serialization_format, identity_nonce) =
created_data_contract_in_serialization_format.data_contract_and_identity_nonce_owned();
let data_contract = DataContract::try_from_platform_versioned(
data_contract_in_serialization_format,
validate,
Expand All @@ -113,7 +113,7 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Cre
{
0 => Ok(CreatedDataContract::V0(CreatedDataContractV0 {
data_contract,
entropy_used: entropy,
identity_nonce,
})),
version => Err(ProtocolError::UnknownVersionMismatch {
method: "CreatedDataContract::versioned_deserialize".to_string(),
Expand All @@ -139,9 +139,9 @@ impl CreatedDataContract {
}
}

pub fn data_contract_and_entropy_owned(self) -> (DataContract, Bytes32) {
pub fn data_contract_and_identity_nonce(self) -> (DataContract, IdentityNonce) {
match self {
CreatedDataContract::V0(v0) => (v0.data_contract, v0.entropy_used),
CreatedDataContract::V0(v0) => (v0.data_contract, v0.identity_nonce),
}
}

Expand All @@ -157,28 +157,22 @@ impl CreatedDataContract {
}
}

pub fn entropy_used_owned(self) -> Bytes32 {
pub fn identity_nonce(&self) -> IdentityNonce {
match self {
CreatedDataContract::V0(v0) => v0.entropy_used,
}
}

pub fn entropy_used(&self) -> &Bytes32 {
match self {
CreatedDataContract::V0(v0) => &v0.entropy_used,
CreatedDataContract::V0(v0) => v0.identity_nonce,
}
}

#[cfg(test)]
pub fn set_entropy_used(&mut self, entropy_used: Bytes32) {
pub fn set_identity_nonce(&mut self, identity_nonce: IdentityNonce) {
match self {
CreatedDataContract::V0(v0) => v0.entropy_used = entropy_used,
CreatedDataContract::V0(v0) => v0.identity_nonce = identity_nonce,
}
}

pub fn from_contract_and_entropy(
pub fn from_contract_and_identity_nonce(
data_contract: DataContract,
entropy: Bytes32,
identity_nonce: IdentityNonce,
platform_version: &PlatformVersion,
) -> Result<CreatedDataContract, ProtocolError> {
match platform_version
Expand All @@ -188,7 +182,7 @@ impl CreatedDataContract {
{
0 => Ok(CreatedDataContractV0 {
data_contract,
entropy_used: entropy,
identity_nonce,
}
.into()),
version => Err(ProtocolError::UnknownVersionMismatch {
Expand Down Expand Up @@ -223,9 +217,13 @@ impl CreatedDataContract {
}

impl CreatedDataContractInSerializationFormat {
pub fn data_contract_and_entropy_owned(self) -> (DataContractInSerializationFormat, Bytes32) {
pub fn data_contract_and_identity_nonce_owned(
self,
) -> (DataContractInSerializationFormat, IdentityNonce) {
match self {
CreatedDataContractInSerializationFormat::V0(v0) => (v0.data_contract, v0.entropy_used),
CreatedDataContractInSerializationFormat::V0(v0) => {
(v0.data_contract, v0.identity_nonce)
}
}
}
}
Loading