-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Initial integration of dynamic contracts and native module loading #1256
Initial integration of dynamic contracts and native module loading #1256
Conversation
src/dynamic_contract.rs
Outdated
} | ||
|
||
#[test] | ||
fn test_contract_move_funds_success() { |
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.
Looks like automation failed on this test (rest pass). I didn't see any failures locally, any ideas?
test dynamic_contract::tests::test_contract_move_funds_success ... error: process didn't exit successfully: /solana/target/debug/deps/solana-2161c8890c108436
(signal: 11, SIGSEGV: invalid memory reference)
🚨 Error: The command exited with status 101
src/dynamic_contract.rs
Outdated
#[test] | ||
fn test_create_library_path_1() { | ||
assert_eq!( | ||
"target/debug/deps/libfoo.dylib", |
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.
Not portable and caught by automation on Linux, will fix
src/dynamic_contract.rs
Outdated
Ok(s) => s, | ||
Err(e) => panic!("{:?} Unable to find {:?} in {:?}", e, ENTRYPOINT, &path), | ||
}; | ||
if let Err(e) = entrypoint(tx, accounts) { |
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 contract only needs tx.userdata
and accounts
, no need to pass in the entire Transaction
src/dynamic_contract.rs
Outdated
account.tokens | ||
} | ||
|
||
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) { |
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 would structure this a little differently where DynamicContract
is a new LoadContract
instruction on SystemContract
. LoadContract
takes the .so name and the contract_id
to associate it with (for now, until we have on-chain storage of contract binaries).
bank.rs
then looks up incoming Transaction contract_id's against the list of loaded contracts and invokes the appropriate entrypoint()
.
Then in a future PR, an internal LoadContract
instruction is performed to load the current budget_contract
so the only hard coded contract that bank.rs
knows about is the system contract (or we just boot the budget_contract
from src/
entirely -- need to refactor the Vote instruction before that's possible though...).
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.
Two or more different transactions involved. First one to load the contract (tx usedata contains filename and id) and subsequent ones to call the loaded contract by id (tx contains contract userdata only). Did you have thoughts on unloading, a second new instruction to SystemContract
?
Also, are you thinking that budget_contract
would also be dynamic?
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.
Two or more different transactions involved...
Yep, exactly.
Did you have thoughts on unloading...
For now let's punt on unloading. But I think unloading can be implicit -- once there are no active Accounts with thecontract_id
for the given .so in bank.rs, that .so can be unloaded from memory. It would be reloaded again once a new active Account is tagged with thatcontract_id
. Unloading will probably be associated with a token value as well. The user pays 100 (N) tokens to make thatcontract_id
available for 100 (M) days.
Also, are you thinking that budget_contract would also be dynamic?
It certainly could be eventually. There's a bunch of code detangling that needs to happen in transaction.rs/bank.rs/.. before that'll be possible though.
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.
Sounds good, will give it a shot
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.
@mvines, would you like me to merge this PR and then start a new one for the structure changes you suggested?
Also, I'll make the changes to pass the keys in this PR, agreed?
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.
There’s no rush to land this so I’d rather the structural changes are made in this PR (keys too)
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.
Awesome. Merge once @mvines is happy with it.
Hey Jack, I realized that we also need to pass the Transaction keys[] into the entry point as well. The entries in keys[] correspond 1-1 to the entries in accounts[], so maybe we zip those two vectors together before passing them in (I don’t feel strongly about zipping either way though). |
zip them so that we don't need to document a semantic coupling |
rust noob question, you guys are suggesting that I zip (iter zip) both keys and accounts together into a vector (to be passed as a buffer) of structs of type [ key: Pubkey, account: Account ]? Also, any name suggestions? :-) |
Yeah that sounds great. Name ideas: AccountInfo maybe? |
6011d16
to
4887376
Compare
@mvines Restructured, let me know what you think |
src/system_contract.rs
Outdated
} | ||
|
||
// TODO where should this live? | ||
struct Temporary { |
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 probably should be a part of bank.rs
src/system_contract.rs
Outdated
} | ||
} | ||
} | ||
SystemContract::Call { contract_id, data } => { |
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.
ah, so instead of Call
we just dispatch the dynamic contracts from https://github.com/solana-labs/solana/blob/master/src/bank.rs#L363
(which is also why the Temporary
stuff can just move into bank.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.
Something like this?
// Call the contract method
// It's up to the contract to implement its own rules on moving funds
if SystemContract::check_id(&tx.contract_id) {
SystemContract::process_transaction(&tx, accounts)
} else if BudgetContract::check_id(&tx.contract_id) {
// TODO: the runtime should be checking read/write access to memory
// we are trusting the hard coded contracts not to clobber or allocate
BudgetContract::process_transaction(&tx, accounts)
} else if {
if let Some(dynamic_contract) = self::check_dynanmic_id(tx.contract_id) {
dynamic_contract.call(tx, accounts);
}
} else {
return Err(BankError::UnknownContractId(tx.contract_id));
}
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.
yep!
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 poses some structural issues. SystemContract::process_transaction()' does not return a value so no way to return the loaded module context back to the bank to be saved in the bank's list. Maybe bank should export both a
load_contract()and
get_contract_by_id()methods rather then these operations being done by
SystemContract?`
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.
hmm, yeah. I think it's ok if the SystemContract::process_transaction()
is a little special and take in another argument from the bank that allows the system contract to save loaded contracts in. I wouldn't expose the entire bank to the system contract, just what it needs
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.
Like, pass in the hash to be populated?
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.
Only slightly less ugly is having system contract call a bank function to register the loaded contract.
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.
passing in the hash seems nice
contracts/move_funds/src/lib.rs
Outdated
let mut infos: Vec<AccountInfo> = Vec::new(); | ||
for (key, account) in keys.iter().zip(&mut accounts).collect::<Vec<_>>() { | ||
infos.push(AccountInfo { key, account }); | ||
} |
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 use the suffix Info
throughout the codebase to mean something like Meta
. So the name AccountInfo
suggests isn't information/metadata about that account, which it's not. How about OwnedAccount
? Suggesting it's an Account with some additional information (the public key of its owner). Or maybe AddressedAccount
? Or perhaps punt on a name and leave it as a tuple?
Also, that code can be written without mut:
let owned_accounts: Vec<_> = keys.into_iter().zip(&accounts).map(|(key, account)| OwnedAccount {key, account}).collect();
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.
Awesome, was looking fror a one-liner for the zip :-)
For the name, maybe KeyedAccount?
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.
no strong opinion. I'd lean toward the tuple. :)
contracts/move_funds/src/lib.rs
Outdated
let keys = vec![Pubkey::default(); 2]; | ||
let mut accounts = vec![Account::default(), Account::default()]; | ||
accounts[0].tokens = 100; | ||
accounts[1].tokens = 1; |
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 happens if this is zero?
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' being the value we assign to `accounts[1].tokens'?
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.
yes
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.
then the result of that test would be that accounts[0].tokens == 0 and accounts[1].tokens == 100. What issue did you notice?
contracts/noop/Cargo.toml
Outdated
|
||
[dependencies] | ||
libloading = "0.5.0" | ||
solana = { path = "../.." } |
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 is rough. Would it be a huge amount of work to move the noop dependencies out of solana into their own workspace?
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 amount of work is not what I'm concerned about. I would like to have example dynamic programs built into the solana project so they are part of the normal dev/test/ci/etc... cycle.
What is your concern with referencing solana in this way?
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 circular reference will make it so it's impossible to get noop
out on its own.
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.
In or out noop will have to depend on Solana (or a subset) in order to share the common interface definitions
src/dynamic_contract.rs
Outdated
/// * Transaction::keys[0..] - contract dependent | ||
/// * TODO BPF specific stuff | ||
/// * userdata - contract specific user data | ||
BPF { userdata: Vec<u8> }, |
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.
Rust convention is to only capitalize the first letter of acronyms. Can you make this Bpf
?
src/system_contract.rs
Outdated
#[derive(Debug)] | ||
pub struct AccountInfo<'a> { | ||
pub key: &'a Pubkey, | ||
pub account: &'a mut Account, |
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.
Hmm, how about dumping the references 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.
Not sure how to how since they are both borrowed by SystemContract::process_transaction()
. Are you suggesting a rework of SystemContract
?
bf9db06
to
f002faf
Compare
0f7f0b4
to
cc320fb
Compare
Worked around thread safety issue: #1314 |
src/system_program.rs
Outdated
@@ -25,6 +28,10 @@ pub enum SystemProgram { | |||
/// * Transaction::keys[0] - source | |||
/// * Transaction::keys[1] - destination | |||
Move { tokens: i64 }, | |||
/// Load a program | |||
/// programn_id - id to associate this program |
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.
nit: programn_id
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.
r+ with the one minor speling nit picked
Looks like unrelated CI test failure, filed issue #1315 |
…end_and_verify_all_nodes_env_num_nodes` (solana-labs#1256)
Addresses first three boxes of #1255.
Notes: