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

Implement #1641 rpc call public functions as read-only - take 2 #2527

Merged
merged 6 commits into from
Apr 27, 2021

Conversation

psq
Copy link
Contributor

@psq psq commented Mar 22, 2021

second attempt in fixing #1641 to allow calling any function as read-only. the call will fail if any write happened during the call.

For example, this allows calling public functions that call use contract-call?, which can not be used in a read-only function, even if the called contract function itself is read-only.

@psq psq requested review from jcnelson and kantai March 22, 2021 07:36
@psq psq changed the title Implement #1641 rpc call public functions as read-only Implement #1641 rpc call public functions as read-only - take 2 Mar 22, 2021
@psq psq self-assigned this Mar 22, 2021
@jcnelson
Copy link
Member

Thanks for sending this @psq! We'll try to get this included in 2.0.12.

},
),
Ok(Some(Err(e))) => match e {
Unchecked(CheckErrors::CostBalanceExceeded(actual_cost, _))
Copy link
Contributor

Choose a reason for hiding this comment

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

I just want to clarify the "path" you're expecting execute_contract to follow to get to this branch of the match statement (where we get the CostBalanceExceeded error).

To me, it seems you want execute_contract to fail at its call to runtime_cost, which will check that the execution costs do not exceed those set by the limits the environment has set. However, the limit is equal to options.read_only_call_limit (as set in line 1250 of this file), and the value for this struct (type ExecutionCost) ultimately comes from ConnectionOptions, which is specified in the Config passed to initialize a new node. So, it seems like there is not a guarantee that write_count or write_length would be 0, meaning the call to runtime_cost in execute_contract may be okay even if there is a write.

Copy link
Member

Choose a reason for hiding this comment

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

I also had a question about this. I could actually see this going either way:

  • This API endpoint explicitly forbids any writes, and causes a CostBalanceExceeded error the moment the code attempts to wrote.
  • This API endpoint explicitly permits writes, but never materializes them. This could be used to allow clients to simulate what would happen if they called a function that performed a write, even though the write never commits.

I can see value in both approaches.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as described in #1641

Make it so that the RPC endpoint used for calling read-only functions can be used to call any functions. Make it so that any mutations are discarded

this makes it possible to call any function, and get a result if no write occurred. Furthermore, a read-only function that makes a contrac-call? is flagged as non read only, even if it is calling other read-only functions and the call-read endpoint will fail. So this only allow calling functions that are so basic it is not so useful in practice, and it prevents composing contracts. You have to use define-public instead, but this is not callable via the call-read endpoint.

So this extends the call-read endpoint to try the function, and return a result if no writes occurred, or a failure if any writes did occur.

So, @jcnelson, this is definitely the former.

@pavitthrap I'm not sure I understand how the node configuration would come into the picture. Either write_count is 0 after the execution and that's a read-only call, or it will fail because it was not read-only.

Copy link
Member

Choose a reason for hiding this comment

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

I think @pavitthrap's concern (correct me if I'm wrong) is that options.read_only_call_limit may have a non-zero values, since this ExecutionCost instance is instantiated from the node's config file, and you're allowed to specify non-zero write sizes. I think the intention for even permitting this at all is that one day, there would be an interface that lets you call read/write contract-calls such that the writes never materialize (or even hit disk).

I think the solution here would be to constract an ExecutionCost from options.read_only_call_limit and explicitly set the write_length and write_count fields to 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, @pavitthrap comment makes sense now. I wasn't expecting these values to be other that 0, but to be safe, I'll set them to 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in 57b29c8

@@ -1456,7 +1456,7 @@ impl PeerNetwork {
};

debug!(
"{:?}: Send GetPoxInv to {:?} for {} rewad cycles starting at {} ({})",
"{:?}: Send GetPoxInv to {:?} for {} reward cycles starting at {} ({})",
Copy link
Member

Choose a reason for hiding this comment

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

Lol wee kant spel

(ok (var-set x val)))
";

const MAIN_CONTRACT: &'static str = "
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you change the variables here to be more descriptive of what we are trying to test? (ex: READ_ONLY_MAIN_CONTRACT)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in 1e2ae7a

testnet/stacks-node/src/tests/integrations.rs Show resolved Hide resolved
})
.map_err(|_| {
ClarityRuntimeError::from(InterpreterError::CostContractLoadFailure)
})?;

clarity_tx.with_readonly_clarity_env(mainnet, sender.clone(), cost_track, |env| {
env.execute_contract(&contract_identifier, function.as_str(), &args, true)
env.execute_contract(&contract_identifier, function.as_str(), &args, false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a comment here explaining why we are setting this to false here (something about how we are using the limits imposed by the cost tracker, which is owned by the environment, to detect any writes).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in 1e2ae7a

@psq psq force-pushed the feat/rpc-call-read-1641-v2 branch from 57b29c8 to 1e2ae7a Compare April 15, 2021 21:56
Copy link
Member

@jcnelson jcnelson left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks @psq!

@jcnelson
Copy link
Member

Just a reminder: please don't merge until 2.0.11 is released.

@psq
Copy link
Contributor Author

psq commented Apr 20, 2021

thank you both for the review and the back and forth, which made this PR better than what it started as.

@philiphacks
Copy link

thanks for the PR @psq

would be great if this can land in 2.0.12 as this blocks arkadiko from dynamically versioning contracts 👀

@psq
Copy link
Contributor Author

psq commented Apr 23, 2021

thanks for the PR @psq

would be great if this can land in 2.0.12 as this blocks arkadiko from dynamically versioning contracts 👀

swapr also relies heavily on this, but would run my own node with this PR deployed if necessary to extract data from contract, but let Connect send transactions through the regular API node (but would rather not have to do this, of course).

Not available yet on mainnet, just on my own mocknet. Happy to share if 2.0.12 gets delayed for any reason.

Copy link
Member

@kantai kantai left a comment

Choose a reason for hiding this comment

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

@psq -- these changes all look good to me. Can you resolve the conflicts in integrations and make sure that the test passes?

@psq
Copy link
Contributor Author

psq commented Apr 27, 2021

@kantai sure, will rebase

@psq psq force-pushed the feat/rpc-call-read-1641-v2 branch from 17a889b to 74d3595 Compare April 27, 2021 01:08
@psq
Copy link
Contributor Author

psq commented Apr 27, 2021

@kantai rebased and all green

@jcnelson jcnelson merged commit f6697f3 into stacks-network:develop Apr 27, 2021
@blockstack-devops
Copy link
Contributor

This pull request 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 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants