-
Notifications
You must be signed in to change notification settings - Fork 43
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
Fork the Ethereum Address Manager + add deployment permissioning #657
Conversation
Hey! I was curious about this PR because I had done HAMT related work recently for a filecoin migration. The use of a HAMT makes sense to me in that these data structures should be relatively small and it's relatively common to use an associative data array with a null value as a set. I think the HAMT performance of using a null value over using a AMT which should be sufficient is a non issue here and any performance difference or storage overhead is nearly neglible. Still just wanted to share my reflection and feedback since in other scenarios it may be an important distinction to use an AMT as a set instead of an HAMT with has a value that is being ignored |
Also looks like there's a panic in a test 😄 |
@snissn Cool, thx for the tip. I used HAMT because there might be chances of dynamically changing the deployers, which HAMT might be better suited and more extendable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad this approach worked! Integrating into genesis should be pretty simple. You'd just place this actor instead of the upstream one at actor ID 10.
I wonder if you want to turn the state field into an enum of Unpermissioned
and Permissioned(Optional<Cid>)
.
The default would be Unpermissioned
(behaving just like the upstream).
Then you have Permissioned(None)
, where no one can deploy contracts.
And Permissioned(Cid)
would contain the Hamt.
Apologies, my finger slipped, I only wanted to submit comments, not reject it outright. |
@raulk Yep, I tried that in the first place, but seems like |
Interesting. I recall seeing enum fields in state or in input/response messages in builtin-actors. Could you scan the codebase there? Maybe start with the miner actor as it's the most complex one. |
@fridrik01 might have an answer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, but we should make sure that tests are passing before merging (its complaining that the new eam actor is not in the blockstore as it has not been loaded at genesis). Fine with addressin that in followup PR as you mention
contracts/binding/src/lib.rs
Outdated
@@ -2,25 +2,31 @@ | |||
#[macro_use] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this change intended? We should either update the build.rs file to format this generated file, or make sure that our tools/ide do not format this file, otherwise it keeps changing between PRs even though it hasn't effectively changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a proposal PR which keeps this file formatted when creating this file in build.rs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
somehow it's auto-formatted and I didnt aware it's pushed, I see if I can revert it.
@@ -183,6 +185,7 @@ fvm_ipld_amt = "0.6.2" | |||
# and this copy-paste is clunky, so at least for those that have it, we should use it. | |||
# Keep the version here in sync with the Makefile! | |||
fil_actors_evm_shared = { git = "https://github.com/filecoin-project/builtin-actors", tag = "v12.0.0" } | |||
fil_actor_eam = { git = "https://github.com/filecoin-project/builtin-actors", tag = "v12.0.0" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
fendermint/actors/eam/src/lib.rs
Outdated
actor_dispatch_unrestricted! { | ||
Constructor => constructor, | ||
Create => create, | ||
Create2 => create2, | ||
CreateExternal => create_external, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If any and all methods need to be dispatched, isn't there a way not to have to enumerate them at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can create macro rules on top of this. I think this is probably the convention used and the actor is quite simple. I guess it's ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, there must be some general method that would simply forward, like handle_message(method_number, params)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, there is, but actor_dispatch_unrestricted
actually contains some checking logic as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ActorCode
trait has this invoke_method
method that you can trivially implement for this actor, and then you don't need to implement all the individual methods, because they all check the same thing. Something like this:
fn invoke_method<RT>(
rt: &RT,
method: fvm_shared::MethodNum,
args: Option<fvm_ipld_encoding::ipld_block::IpldBlock>,
) -> Result<Option<fvm_ipld_encoding::ipld_block::IpldBlock>, ActorError>
where
RT: Runtime,
RT::Blockstore: Clone,
{
Self::ensure_deployer_allowed(rt)?;
EamActor::invoke_method(rt, method, args)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any documentation on actor_dispatch_unrestricted
so can't say what's the difference between that and actor_dispatch
, but the only check I see it does is the method number. That will be performed by EamActor::invoke_method
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @aakoshh. Performing the permissioning at the invoke_method entrypoint should be enough (we should leave the Constructor) out of this extra layer of permissioning, since it can only be invoked by the system actor and it already performs that check.
https://github.com/filecoin-project/builtin-actors/blob/master/actors/eam/src/lib.rs#L237
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way we can get rid of every method here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated accordingly
fendermint/actors/eam/src/state.rs
Outdated
|
||
impl State { | ||
pub fn new<BS: Blockstore>(store: &BS) -> Result<State, ActorError> { | ||
let empty_deployers = DeployerMap::empty(store, DEFAULT_HAMT_CONFIG, "empty").flush()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this "empty"
thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an identifier for the map
, it's not stored in the storage, but just in memory. I took this from the actor development convention. I think this is a identifier for the map or sth along that line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, approved by accident.
fendermint/actors/eam/src/lib.rs
Outdated
actor_dispatch_unrestricted! { | ||
Constructor => constructor, | ||
Create => create, | ||
Create2 => create2, | ||
CreateExternal => create_external, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @aakoshh. Performing the permissioning at the invoke_method entrypoint should be enough (we should leave the Constructor) out of this extra layer of permissioning, since it can only be invoked by the system actor and it already performs that check.
https://github.com/filecoin-project/builtin-actors/blob/master/actors/eam/src/lib.rs#L237
fendermint/actors/eam/src/lib.rs
Outdated
actor_dispatch_unrestricted! { | ||
Constructor => constructor, | ||
Create => create, | ||
Create2 => create2, | ||
CreateExternal => create_external, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way we can get rid of every method here.
fendermint/actors/eam/src/state.rs
Outdated
const NO_RESTRICTION_MODE: u8 = 0; | ||
const WHITELIST_MODE: u8 = 1; | ||
|
||
impl Serialize for PermissionMode { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
match self { | ||
PermissionMode::NoRestriction => { | ||
let inner: (_, Vec<u8>) = (NO_RESTRICTION_MODE, vec![]); | ||
Serialize::serialize(&inner, serde_tuple::Serializer(serializer)) | ||
} | ||
PermissionMode::Whitelist(cid) => { | ||
let inner = (WHITELIST_MODE, cid.to_bytes()); | ||
Serialize::serialize(&inner, serde_tuple::Serializer(serializer)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl<'de> Deserialize<'de> for PermissionMode { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let (mode, bytes): (u8, Vec<u8>) = | ||
Deserialize::deserialize(serde_tuple::Deserializer(deserializer))?; | ||
Ok(match mode { | ||
NO_RESTRICTION_MODE => PermissionMode::NoRestriction, | ||
WHITELIST_MODE => PermissionMode::Whitelist( | ||
Cid::from_bytes(&bytes).map_err(|_| Error::custom("invalid cid"))?, | ||
), | ||
_ => return Err(Error::custom("invalid permission mode")), | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should not be needing the custom serde here! According to @vmx here, libipld_serde_cbor supports externally tagged enums (not clear to me how it lays them out though), as well as Option
types. We might need to add the #[repr] tag so serde knows which type to use for the discriminator. Perhaps that's why it didn't work for you originally?
fendermint/vm/genesis/src/lib.rs
Outdated
@@ -40,6 +40,9 @@ pub struct Genesis { | |||
/// highest possible fidelity when we are deriving a genesis file in IPC, | |||
/// where the parent subnet tracks collateral. | |||
pub validators: Vec<Validator<Collateral>>, | |||
/// List of contract addresses that are allowed to deploy contracts in the subnet. If | |||
/// empty, indicates there is no restriction on who can deploy contracts | |||
pub contract_deployers: Vec<SignerAddr>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's roll back the genesis changes here, and instead have users who want to use this feature fork Fendermint and customize genesis within init. Rationale: there will be plenty of customizations possible down the line, and it won't scale to expose them all through genesis. In the future, these will be Wasm modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But for now, how are they going to config this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that means we put the addresses in the source code? But I think this one should be in genesis as we should not allow the deployers to change regardless how source code changes. If deployers need to change, we should also go through txn submission.
This one addresses "Add permissioning to the Ethereum Address Manager".
The approach is pretty simple, it creates an HAMT that tracks the whitelisted deployers. Only when the
caller
is in the HAMT, will thecaller
be allowed to create contracts. This PR does not copy paste the originalEAM
implementation but imports it, so that it's easier to maintain.Follow up PR: