In the previous Road to Neo3 article, we discussed the usage of script hashes on Neo2 and the issues they could cause for developers and the network. Although the potential for problems related to data migration could be mitigated through contract redirection records, other unresolved issues with the solution prompted an alternate approach.

Work began on the implementation of unique contract IDs following a suggestion by Erik Zhang, who explained the benefit of the change:

“The main difference is that with the GUID you don’t need to add redirection records, and the contract can be upgraded any times without recording additional information to prevent old contracts from being redeployed. Because if an old contract is deployed, a new GUID is created.”

In addition to removing the need to migrate contract storage, another advantage of this approach is that contracts will no longer have their identifier (script hash) changed after an update. One deterministic ID is assigned to each deployed contract, persisting between code updates. This means that service providers who integrate with Neo contracts will no longer need to manually update their infrastructure whenever a contract is updated.

Contract ID implementation

Zhang followed his proposal with a naive implementation, replacing script hashes with UIDs and providing a deterministic UID generation scheme.

While reviewing the proposal, NGD software developer Mengyu Liu highlighted that the simple generation method could allow for the possibility of hash collisions, which could be used to attack contract storage.

Noting the need for safe and consistent UID generation, Liu proposed two alternate methods for generating the required contract IDs. After initially suggesting the use of the block height, transaction index, and syscall counter to generate keys, a global counter was proposed.

The strength of the global counter approach lies in its simplicity. Each time a contract is deployed to the Neo3 blockchain, it uses the counter to determine and take the next available ID number and the counter is incremented. This process is repeated on each new deployment, ensuring each contract receives a unique ID number.

Liu provided implementations for both approaches, however the global counter proved to be the favorite among developers as it negated concern over hash collisions. One downside that was discovered to this approach was that native contracts could not use the contract ID directly, since the global counter (and therefore all contract IDs) would be affected if a new native contract was added in the future.

This flaw was resolved by core developer Shargon, who suggested that native contracts should have negative values, allowing them to leverage the same global counter but without affecting normal contract IDs when new native contracts are added. Each new native contract would instead have a decremented ID (-1, -2, -3.. etc).

Despite being initially implemented with the intention of preventing DoS attacks, unique contract IDs will offer numerous advantages to developers, third parties, and even help reduce the blockchain size.