Skip to content
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

Proposal: Versioning contracts (reboot) #1098

Closed
lgalabru opened this issue Aug 21, 2019 · 18 comments
Closed

Proposal: Versioning contracts (reboot) #1098

lgalabru opened this issue Aug 21, 2019 · 18 comments

Comments

@lgalabru
Copy link
Contributor

I’m currently working on #1078, and I’d like to re-open the debate with had about versioning :)

I think we can all agree on the fact that versioning is important, but I’ll state the obvious.
We are currently working on a version 2 of blockstack, this version 2 will itself be versioned (fixes, minor improvements, major improvements, etc). Our project is relying on a few dependencies, also versioned, and versioning plays an important role in the stability of our project.
As a developer, when I want to add a dependency to a solution, I have crates.io, where I can find libraries, I can see the different versions available, the one deprecated, etc.
We’ve been able to build great software ecosystems thanks to versioning. Versioning is as important as the identifier qualifying a solution: it’s part of the contract between a project and its dependencies, an executable and its runtime, a mobile app and its SDK, an HTTP client and its API, etc.

The only counter example I can think of is Smart contract with Solidity and we all agree on the fact that it should not set the standard of smart contracts.
Smart contracts repositories on Github are alive, they keep receiving some commits and being re-deployed.

With Clarity, we are introducing clean, human readable names.
Why not also including versioning?
If we envision that developers will end up appending a “_2”/“_v2”/“_version_2” / “_rev2” etc in the name part of the contract’s principal, why not allocating a version field in the contract principal for that?

A clean, native versioning mechanism would be beneficial for the developers, and more importantly, the end users (ex: wallet warning when a user is about to send a transaction to an outdated contract).

What's the downside of versioning contracts?

@jcnelson
Copy link
Member

jcnelson commented Aug 22, 2019

No.

Versioning as you describe it fundamentally breaks the trust model for smart contracts. It puts smart contracts under the same trust model as Web 2.0 servers, which is unacceptable.

The trust model for smart contracts is that once deployed, a smart contract's code is immutable and irrevocable. Moreover, smart contract state is as immutable as the transactions and blocks that encode its state-transitions. Changing a smart contract's code or state necessarily requires a blockchain reorg.

The lack of versioning is a feature, not a bug -- it is precisely what makes smart contracts a "can't-be-evil" form of distributed computing. Once you, the user, select a smart contract you want to use, no one can take it away from you. Smart contracts are not web apps, and that's intentional.

The ability to publish a "newer version" of a smart contract and have all subsequent transactions automatically and implicitly interact with it (even if the user can opt out) allows the developer to be evil towards users. A "newer version" may simply steal the user's funds. A "newer version" may inflate the token or asset supply. A "newer version" can alter the semantics of existing methods, and break other smart contracts. The ability to publish a "newer version" means that a hacker who steals the developer's keys can cause harm to users as well.

I cannot support adding versioning like this to Stacks v2. If the user wants to use a "newer version" of the smart contract, then the user (not the developer, and not the blockchain) must explicitly select which "version" they want to use when they craft their transactions.

That said, I am open to building a versioning "convention" whereby a smart contract developer can provide guidance for users as to which smart contract instance to use, and which instances have bugs and should be avoided. But the system cannot enable developers to choose for users -- the user must always select which smart contracts they interact with.

@lgalabru
Copy link
Contributor Author

Ok, there is a big misunderstanding and I think we are on the same page.

The chain of execution would not be dynamic, and the developer would be fully qualifying the version of the contract that have to be used (contract-call! 'issuer-principal.contract-name.1).

@lgalabru
Copy link
Contributor Author

As a developer pushing the following contract:

(define-public (external-call (value int)) 
    contract-call! 'issuer-principal.contract-name.1 do-something value)

If I'm getting a notice from the analysis informing me that issuer-principal.contract-name.1 is not the latest version and that I should check the version 2, I think that the chances for the version 2 patching a (maybe super critical?) bug from version 1 are higher than the version 2 being a malicious version. And if that's actually the case, I can still choose to ignore the notice.
The analysis, or the vm, in any way, would never make this decision for you.

@jcnelson
Copy link
Member

jcnelson commented Aug 22, 2019

The chain of execution would not be dynamic, and the developer would be fully qualifying the version of the contract that have to be used (contract-call! 'issuer-principal.contract-name.1).

I see. This is very different than the versioning semantics offered by your example of crates.io, where the full version does not need to be specified to install a dependency, and old versions can be deleted (even if by accident). Using an example that behaves differently than the proposal is what led to the misunderstanding.

If I'm getting a notice from the analysis informing me that issuer-principal.contract-name.1 is not the latest version and that I should check the version 2, I think that the chances for the version 2 patching a (maybe super critical?) bug from version 1 are higher than the version 2 being a malicious version.

This is not a line of thinking we should encourage. This line of thinking leads to the lazy (and harmful) behavior of "always use the latest version when deploying." Attackers have already exploited this laziness in the wild many times over in software repositories like crates.io and npm by uploading a "newer" dependency that is malicious, and then having their victims' CI processes pull in the malware on the next deployment. The same kind of attack can happen to us if we have first-class versioning -- even if the tooling forces you to pick a version, the fact that developers will more-likely-than-not simply pick the latest version gives attackers the ability to get you to depend on their malware (and thus harm your users).

The behavior we should be encouraging is to get smart contract authors to carry out end-to-end audits their code, including all their dependencies, before deploying it. Our system design should make it difficult for developers to avoid doing this. The absence of a versioning scheme helps encourage this behavior, because it forces developers to take a look at the particular dependencies they select.

The analysis, or the vm, in any way, would never make this decision for you.

Ours won't. But if we add versioning as a first-class name component, other tools will make selections on developers' behalves out of misguided convenience, and we won't be able to prevent it.

@jcnelson
Copy link
Member

As a general design principle in Clarity, developer convenience can usually be sacrificed to improve user security. This is why we went with a LISP variant that is not Turing-complete. This is why the language is interpreted (not compiled), and no other languages are supported. This is why we don't support re-entrancy or dynamic dispatch. First-class versioning is a developer convenience, but as your example illustrates, it can easily be used to hurt user security.

@lgalabru
Copy link
Contributor Author

The behavior we should be encouraging is to get smart contract authors to carry out end-to-end audits their code

Thank you for bringing this subject.
In node, you can't reasonably audit your dependencies, because of the "left-pad" library syndrome.
In solidity, you could be relying on libraries that have been heavily audited. The only library that I would rely on in Solidity is OpenZeppelin.

IMHO, auditing code requires some special skills, and a developer (even good) is not necessarily a good auditor.
Auditing smart contracts is a full time job and an industry, and I think we should do our best for building a first class audit-able ecosystem.
Traceability is going in this direction.
As a smart contract developer, if I really, really have to rely on another contract, then I would love to have the tooling for exploring, benchmarking and shaking these dependencies - some kind of crates.io (pardon the recurring reference, but there's no such things in the smart contract world, today), focused on security, traceability and audits.

Mockup

Helping developers making the best decision when it comes to rely on a dependency is nothing but a security concern. Not a convenience.

@jcnelson
Copy link
Member

jcnelson commented Aug 22, 2019

Just writing down some notes from an earlier conversation:

  • Smart contract systems like Ethereum tend to do vendoring -- each smart contract developer will bundle a duplicate copy of other dependent smart contracts. However, we think that this is mainly due to the fact that it is hard to inspect the on-chain EVM code and determine that it is semantically equivalent to a given smart contract.

  • A key value-add that would be realized from versioning (regardless of how it is implemented) is that it gives users the ability to detect the latest incarnation of a particular smart contract. For example, users might want to use the latest version of an on-chain game. However, we should be careful how we approach this design pattern -- we will need to educate users and developers to be cautious when switching to a later version. Users need to verify somehow that the newest version was produced with honest intentions, and developers need a way to communicate this to users via a separate authenticated channel. More thought needs to be put into this, as well as the tooling that will enable this.

  • If developers find versioning useful, they will implement it via a convention even if we do nothing.

  • If we later regret not adding first-class versioning, we can add it in later with a soft fork -- miners can simply reject requests to create smart contracts that don't follow the versioning convention, and subsequent node releases can begin treating the version component of the name as a first-class version provided they continue to use the same serialization logic as before.

@moxiegirl
Copy link
Contributor

  • Smart contract systems like Ethereum tend to do vendoring -- each smart contract developer

-- do you mean versioning?

@jcnelson
Copy link
Member

Hey @moxiegirl, I meant "vendoring" :) https://stackoverflow.com/questions/26217488/what-is-vendoring

I did not know this at first, but it is apparently common practice for Solidity developers to avoid depending on already-deployed smart contracts. Instead, they are more prone to just make copies of them and deploy them themselves.

@jcnelson
Copy link
Member

jcnelson commented Sep 3, 2019

A key value-add that would be realized from versioning (regardless of how it is implemented) is that it gives users the ability to detect the latest incarnation of a particular smart contract. For example, users might want to use the latest version of an on-chain game. However, we should be careful how we approach this design pattern -- we will need to educate users and developers to be cautious when switching to a later version. Users need to verify somehow that the newest version was produced with honest intentions, and developers need a way to communicate this to users via a separate authenticated channel. More thought needs to be put into this, as well as the tooling that will enable this.

I wanted to revisit this concept once more, having spoken with @kantai about what sorts of things miners will need to commit to when instantiating new smart contracts.

With the imminent addition of the (at-block ... intrinsic, we are effectively adding a way for developers to "upgrade" smart contracts in a way that is not evil (h/t to @lgalabru for the idea!). It's more akin to a "smart contract fork" -- the old smart contract remains usable, but any state added to the old version after the block height in the (at-block ... will be ignored by the new version. Transactions and smart contracts would need to specify whether or not they want to call into the old or new version, but the point is that both options are still available and are equally usable.

This suggests to me that all smart contracts need to have a monotonically-increasing sequence number, similar to an account nonce. The nonce would be part of the fully-qualified contract name, and would be given to contract-call!.

In particular, we would want the protocol itself to compel miners to commit to all versions of the smart contract. To do so, miners would construct a MARF hash that cryptographically commit to:

  • the latest smart contract nonce for each smart contract instantiated in the block
  • the transaction ID of each smart contract instantiated in the block
  • the ancestor MARF hash that committed to the previous version of the contract, if applicable.

This gives users (in particular, light clients and wallets) the ability to request the latest version of a smart contract, as well as query and walk to all prior versions of the contract, all while verifying their inclusion in the blockchain. This, I think, strikes the right balance between developers needing to ship updates to their smart contracts against the user's need to continue using whichever smart contract version(s) they want.

Thoughts?

@stale
Copy link

stale bot commented Apr 17, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 17, 2020
@stale
Copy link

stale bot commented Apr 25, 2020

This issue has been automatically closed. Please reopen if needed.

@stale stale bot closed this as completed Apr 25, 2020
@kantai kantai reopened this Jun 18, 2020
@stale stale bot removed the stale label Jun 18, 2020
@kantai
Copy link
Member

kantai commented Jun 18, 2020

Reopening this issue, because I think we still need to come to an agreement here, and the stale bot is not how we want to get there 😄

@stale
Copy link

stale bot commented Dec 15, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Dec 15, 2020
@stale
Copy link

stale bot commented Dec 22, 2020

This issue has been automatically closed. Please reopen if needed.

@stale stale bot closed this as completed Dec 22, 2020
@lgalabru lgalabru reopened this Apr 5, 2021
@stale stale bot removed the stale label Apr 5, 2021
@stale
Copy link

stale bot commented Apr 16, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Apr 16, 2022
@stale
Copy link

stale bot commented Jun 12, 2022

This issue has been automatically closed. Please reopen if needed.

@blockstack-devops
Copy link
Contributor

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@stacks-network stacks-network locked as resolved and limited conversation to collaborators Nov 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants