From e9a5a933664b755887c7acd9888d5ba584877af0 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Mon, 15 Nov 2021 18:55:22 +0000 Subject: [PATCH 01/41] runtime: Cargo feature to make block production faster Add a compilation-time feature to shorten the block interval to 3 seconds. Intended mainly for testing. Use the `--features short-block-time` switch to Cargo to enable. --- runtime/Cargo.toml | 1 + runtime/src/lib.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 1c0c972..f947e73 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -248,6 +248,7 @@ path = "../pallets/pp" [features] default = ['std'] +short-block-time = [] runtime-benchmarks = [ 'frame-benchmarking', 'frame-support/runtime-benchmarks', diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 31fa527..f9951df 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -125,7 +125,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { /// up by `pallet_aura` to implement `fn slot_duration()`. /// /// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 60_000; //1 min +#[cfg(not(feature = "short-block-time"))] +pub const MILLISECS_PER_BLOCK: u64 = 60_000; // 1 min +#[cfg(feature = "short-block-time")] +pub const MILLISECS_PER_BLOCK: u64 = 3_000; // 3 sec // NOTE: Currently it is not possible to change the slot duration after the chain has started. // Attempting to do so will brick block production. From 6680699299cc339c4d3278929ed8291cf4909718 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 10:16:56 +0100 Subject: [PATCH 02/41] Attempt to use short block times with CI --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f7d495..b92d81f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,9 +67,9 @@ jobs: with: submodules: recursive - name: Build - run: cargo build --release + run: cargo build --release --features short-block-time - name: Run tests - run: cargo test --release + run: cargo test --release --features short-block-time - name: Prepare functional tests uses: actions/setup-python@v2 with: From a2f451ad66bc62d3b53f1748f9446eddbce391b8 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 11:15:00 +0100 Subject: [PATCH 03/41] Add test as artifacts from CI available to download on failure --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b92d81f..0f00a7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,3 +80,9 @@ jobs: run: python -m pip install 'scalecodec == 0.11.18' - name: Run functional tests run: python test/functional/test_runner.py + - name: Save test artifacts on failure + uses: actions/upload-artifact@v2 + if: failure() + with: + name: test-artifacts + path: /tmp/mintlayer* From 217e4f599cb695e6bf27d9ad86a388f847ea8ae1 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 13:08:30 +0100 Subject: [PATCH 04/41] Print list of tests running to simplify catching issues + reduce timeout --- test/functional/test_framework/test_node.py | 4 ++-- test/functional/test_runner.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index a457e96..67e7624 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -52,8 +52,8 @@ def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mo if timewait: self.rpc_timeout = timewait else: - # Wait for up to 600 seconds for the RPC server to respond - self.rpc_timeout = 600 + # Wait for up to 120 seconds for the RPC server to respond + self.rpc_timeout = 120 if binary is None: self.binary = os.getenv("NODEEXE", "mintlayer-core") else: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 0ce8171..4ec72ba 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -326,6 +326,13 @@ def get_next(self): log_stderr)) if not self.jobs: raise IndexError('pop from empty list') + + # print test names that are running + running_test_names = [test_data[0] for test_data in self.jobs] + running_test_names_list = ["{}{}{}".format(BOLD[1], nm, BOLD[0]) for nm in running_test_names] + running_test_names_list = ", ".join(running_test_names_list) + logging.debug("Tests currently running: %s", running_test_names_list) + while True: # Return first proc that finishes time.sleep(.5) @@ -336,6 +343,7 @@ def get_next(self): # providing useful output. proc.send_signal(signal.SIGINT) if proc.poll() is not None: + # check if the test has finished log_out.seek(0), log_err.seek(0) [stdout, stderr] = [l.read().decode('utf-8') for l in (log_out, log_err)] log_out.close(), log_err.close() From 44eff6aa007fb1d1c31c3b5ced5ad5957abc667a Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 13:10:53 +0100 Subject: [PATCH 05/41] Disable parallelism of functional tests in CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f00a7d..5852630 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: - name: Install scalecodec run: python -m pip install 'scalecodec == 0.11.18' - name: Run functional tests - run: python test/functional/test_runner.py + run: python test/functional/test_runner.py --jobs 1 - name: Save test artifacts on failure uses: actions/upload-artifact@v2 if: failure() From f20d147844f348efa96df0b1b3a07d3cbf2f89b9 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 13:16:52 +0100 Subject: [PATCH 06/41] Minor: Add plural for tests listing --- test/functional/test_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 4ec72ba..a279232 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -331,7 +331,7 @@ def get_next(self): running_test_names = [test_data[0] for test_data in self.jobs] running_test_names_list = ["{}{}{}".format(BOLD[1], nm, BOLD[0]) for nm in running_test_names] running_test_names_list = ", ".join(running_test_names_list) - logging.debug("Tests currently running: %s", running_test_names_list) + logging.debug("Test%s currently running: %s", "s" if len(running_test_names) > 1 else "", running_test_names_list) while True: # Return first proc that finishes From 61600109f60f184b9d8373dbb3e36f372b67b204 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 15:09:45 +0100 Subject: [PATCH 07/41] Use paritydb instead of rocksdb on CI --- test/functional/test_framework/test_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 67e7624..e737f9a 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -65,6 +65,7 @@ def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mo port_rpc = rpc_port(i) port_p2p = p2p_port(i) self.args = [self.binary, "--dev", + "--database", "paritydb-experimental", "--base-path", self.datadir, "--log", "trace", "--name", "testnode%d" % i, From 697b9bb98f23a3e1e07aa0b83fac2d289b6dbda5 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 15:09:58 +0100 Subject: [PATCH 08/41] Rename artifacts --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5852630..36429ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,5 +84,5 @@ jobs: uses: actions/upload-artifact@v2 if: failure() with: - name: test-artifacts + name: functional-tests-logs path: /tmp/mintlayer* From e380f2d35296b185208f5454c4a0ffab3f08d537 Mon Sep 17 00:00:00 2001 From: Samer Afach Date: Tue, 16 Nov 2021 15:54:45 +0100 Subject: [PATCH 09/41] Enforce print order by flushing on WARNING print --- test/functional/test_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index a279232..c4dd2ed 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -411,7 +411,7 @@ def check_script_list(src_dir): python_files = set([t for t in os.listdir(script_dir) if t[-3:] == ".py"]) missed_tests = list(python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS))) if len(missed_tests) != 0: - print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests))) + print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % (BOLD[1], BOLD[0], str(missed_tests)), flush=True) if os.getenv('TRAVIS') == 'true': # On travis this warning is an error to prevent merging incomplete commits into master sys.exit(1) From a9853ec952c4cbc5c16508c3bb2bb03c56334abd Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Mon, 15 Nov 2021 16:12:15 +0700 Subject: [PATCH 10/41] Refactor staking-related queries to separate file --- .../test_framework/mintlayer/_staking.py | 13 ------ .../test_framework/mintlayer/staking.py | 42 +++++++++++++++++++ .../test_framework/mintlayer/utxo.py | 37 ++++------------ 3 files changed, 49 insertions(+), 43 deletions(-) delete mode 100644 test/functional/test_framework/mintlayer/_staking.py create mode 100644 test/functional/test_framework/mintlayer/staking.py diff --git a/test/functional/test_framework/mintlayer/_staking.py b/test/functional/test_framework/mintlayer/_staking.py deleted file mode 100644 index ef74968..0000000 --- a/test/functional/test_framework/mintlayer/_staking.py +++ /dev/null @@ -1,13 +0,0 @@ -import substrateinterface - - -class Staking(object): - - """ Query the node for the staking ledger """ - def get_staking_ledger(self): - query = self.substrate.query_map( - module='Staking', - storage_function='Ledger' - ) - - return ((h, o.value) for (h, o) in query) diff --git a/test/functional/test_framework/mintlayer/staking.py b/test/functional/test_framework/mintlayer/staking.py new file mode 100644 index 0000000..722e9ac --- /dev/null +++ b/test/functional/test_framework/mintlayer/staking.py @@ -0,0 +1,42 @@ +import substrateinterface + + +class Staking(object): + def __init__(self, substrate): + self.substrate = substrate + + """ Query the node for the staking ledger """ + def get_staking_ledger(self): + query = self.substrate.query_map( + module='Staking', + storage_function='Ledger' + ) + + return ((h, o.value) for (h, o) in query) + + """ accesses current era """ + def current_era(self): + query = self.substrate.query( + module='Staking', + storage_function='CurrentEra' + ) + return query + + """ gets the staking ledger of the given key """ + def get_ledger(self, keypair): + query = self.substrate.query_map( + module='Staking', + storage_function='Ledger' + ) + + matching = lambda e: e[0].value == keypair.ss58_address + + return filter(matching, ((h, o.value) for (h, o) in query)) + + """ returns what era for the user to be able to withdraw funds """ + def withdrawal_era(self, keypair): + ledger = list(self.get_ledger(keypair)) + if ledger: + return ledger[0][1]['unlocking'][0]['era'] + else: + print("no funds to withdraw") diff --git a/test/functional/test_framework/mintlayer/utxo.py b/test/functional/test_framework/mintlayer/utxo.py index a8bf3a5..20b512a 100644 --- a/test/functional/test_framework/mintlayer/utxo.py +++ b/test/functional/test_framework/mintlayer/utxo.py @@ -6,6 +6,7 @@ import scalecodec import os import logging +from staking import Staking """ Client. A thin wrapper over SubstrateInterface """ class Client(): @@ -22,6 +23,8 @@ def __init__(self, url="ws://127.0.0.1", port=9944): type_registry=custom_type_registry ) + self.staking = Staking(self.substrate) + """ SCALE-encode given object in JSON format """ def encode_obj(self, ty, obj): return self.substrate.encode_scale(ty, obj) @@ -72,47 +75,21 @@ def get_staking_count(self, stash_keypair): return filter(matching , staking_count) - # TODO: move to a separate file """ accesses pallet-staking to retrieve the ledger """ def get_staking_ledger(self): - query = self.substrate.query_map( - module='Staking', - storage_function='Ledger' - ) + return self.staking.get_staking_ledger() - return ((h, o.value) for (h, o) in query) - - # TODO: move to a separate file """ accesses current era """ def current_era(self): - query = self.substrate.query( - module='Staking', - storage_function='CurrentEra' - ) - return query + return self.staking.current_era() - # TODO: move to a separate file """ gets the staking ledger of the given key """ def get_ledger(self, keypair): - query = self.substrate.query_map( - module='Staking', - storage_function='Ledger' - ) - - matching = lambda e: e[0].value == keypair.ss58_address - - return filter(matching, ((h, o.value) for (h, o) in query)) + return self.staking.get_ledger() - # TODO: move to a separate file """ returns what era for the user to be able to withdraw funds """ def withdrawal_era(self, keypair): - ledger = list(self.get_ledger(keypair)) - if ledger: - return ledger[0][1]['unlocking'][0]['era'] - else: - print("no funds to withdraw") - - + return self.staking.withdrawal_era(keypair) """ Submit a transaction onto the blockchain """ def submit(self, keypair, tx): From b11d40aa175ca185964c220edd373cb5961fb7f5 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Mon, 8 Nov 2021 15:03:22 +0000 Subject: [PATCH 11/41] utxo: Decrease the number of time lock proptests These tests take too long in debug mode, so decrease the number of randomly generated inputs by factor of 16, which gives us 16 tests run by default. This should be enough to cover all the interesting code paths most of the time. --- pallets/utxo/src/tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pallets/utxo/src/tests.rs b/pallets/utxo/src/tests.rs index 9cd3445..214e06e 100644 --- a/pallets/utxo/src/tests.rs +++ b/pallets/utxo/src/tests.rs @@ -630,7 +630,17 @@ fn test_send_to_address() { }) } +// Proptest config to decrease the number of test runs by the factor of 16 (for expensive tests). +fn proptest_expensive() -> proptest::test_runner::Config { + let mut config = proptest::test_runner::Config::default(); + config.cases /= 16; + config +} + proptest! { + // These tests are fairly expensive, run fewer of them. + #![proptest_config(proptest_expensive())] + #[test] fn prop_gen_block_time_real_works(bt in gen_block_time_real()) { // This generator should not sample block-based time. From a2fb19c79b9d5553aef15b1362e8796f7a546c1a Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Mon, 18 Oct 2021 11:57:45 +0300 Subject: [PATCH 12/41] pools: Fetch AccountId of the smart contract from ChainExtension Instead of allowing the smart contract caller to define the AccountId of the contract, fetch the value from the ChainExtension's runtime memory. This allows the programmable pool to force the smart contract to only spend its own funds. --- pallets/pp/src/lib.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pallets/pp/src/lib.rs b/pallets/pp/src/lib.rs index 36055b8..226f8a0 100644 --- a/pallets/pp/src/lib.rs +++ b/pallets/pp/src/lib.rs @@ -27,7 +27,7 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -use codec::Encode; +use codec::{Decode, Encode}; pub use frame_support::{ construct_runtime, dispatch::Vec, @@ -267,10 +267,18 @@ impl ChainExtension for Pallet< where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { + // Fetch AccountId of the caller from the ChainExtension's memory + // This way the progrmmable pool can force the caller of the ChainExtension + // to only spend their own funds as `ContractBalances` will be queried + // using `acc_id` and user cannot control the value of this variable + let mut env = env.buf_in_buf_out(); + let acc_id = env.ext().address().encode(); + let acc_id: T::AccountId = T::AccountId::decode(&mut &acc_id[..]) + .map_err(|_| "Failed to get smart contract's AccountId")?; + match func_id { x if x == ChainExtensionCall::Transfer as u32 => { - let mut env = env.buf_in_buf_out(); - let (acc_id, dest, value): (T::AccountId, T::AccountId, u128) = env.read_as()?; + let (dest, value): (T::AccountId, u128) = env.read_as()?; if !>::get(&dest).is_none() { return Err(DispatchError::Other( @@ -281,9 +289,6 @@ impl ChainExtension for Pallet< send_p2pk_tx::(&acc_id, &dest, value)? } x if x == ChainExtensionCall::Balance as u32 => { - let mut env = env.buf_in_buf_out(); - let acc_id: T::AccountId = env.read_as()?; - let fund_info = >::get(&acc_id).ok_or(DispatchError::Other( "Contract doesn't own any UTXO or it doesn't exist!", ))?; @@ -294,13 +299,8 @@ impl ChainExtension for Pallet< x if x == ChainExtensionCall::Call as u32 => { // `read_as_unbounded()` has to be used here because the size of `data` // is only known during runtime - let mut env = env.buf_in_buf_out(); - let (acc_id, dest, selector, mut data): ( - T::AccountId, - T::AccountId, - [u8; 4], - Vec, - ) = env.read_as_unbounded(env.in_len())?; + let (dest, selector, mut data): (T::AccountId, [u8; 4], Vec) = + env.read_as_unbounded(env.in_len())?; if >::get(&dest).is_none() { return Err(DispatchError::Other("Destination doesn't exist")); From 46d6347fe328452163fe0c965483fe825b8dc0b8 Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Mon, 18 Oct 2021 16:59:34 +0300 Subject: [PATCH 13/41] utxo: Improve Destination::CallPP spending validation When PP is constructing the final TX that spends the smart contract's funds, save the outpoint hashes of the vins to a separate storage. This indicates to the local node that the smart contract has spent its funds and when it receives a TX trying to spend CallPPs of a given smart contract, the outpoint hashes in the witness field can be queried from this storage and if they are found, the TX is valid (or at least the local node agrees with the TX and accepts it). --- pallets/pp/src/lib.rs | 4 +- pallets/utxo/src/lib.rs | 80 +++++++++++++++++++++----------------- traits/utxo-api/src/lib.rs | 4 +- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/pallets/pp/src/lib.rs b/pallets/pp/src/lib.rs index 226f8a0..d8e6e31 100644 --- a/pallets/pp/src/lib.rs +++ b/pallets/pp/src/lib.rs @@ -144,7 +144,7 @@ fn send_p2pk_tx( ensure!(fund_info.funds >= value, "Caller doesn't have enough funds"); let outpoints = fund_info.utxos.iter().map(|x| x.0).collect::>(); - T::Utxo::send_conscrit_p2pk(caller, dest, value, &outpoints) + T::Utxo::submit_c2pk_tx(caller, dest, value, &outpoints) } /// Create Contract-to-Contract transfer that allows smart contracts to @@ -169,7 +169,7 @@ fn send_c2c_tx( ))?; let outpoints = fund_info.utxos.iter().map(|x| x.0).collect::>(); - T::Utxo::send_conscrit_c2c(caller, dest, fund_info.funds, data, &outpoints) + T::Utxo::submit_c2c_tx(caller, dest, fund_info.funds, data, &outpoints) } impl ProgrammablePoolApi for Pallet diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index 1b16303..0bc1c2e 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -515,6 +515,10 @@ pub mod pallet { pub(super) type StakingCount = StorageMap<_, Identity, T::AccountId, (u64, Value), OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn ectl_store)] + pub(super) type EctlStore = StorageMap<_, Identity, H256, bool>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::metadata(T::AccountId = "AccountId")] @@ -1040,13 +1044,22 @@ pub mod pallet { log::info!("TODO validate spending of OP_CREATE"); } Destination::CallPP(_, _, _) => { - let spend = - u16::from_le_bytes(input.witness[1..].try_into().or_else(|_| { - Err(DispatchError::Other( - "Failed to convert witness to an opcode", - )) - })?); - ensure!(spend == 0x1337, "OP_SPEND not found"); + // 32-byte hash + 1 byte length + ensure!( + input.witness.len() == 33, + "Witness field doesn't contain valid data" + ); + + let hash: [u8; 32] = input.witness[1..] + .try_into() + .map_err(|_| DispatchError::Other("Failed to convert the slice"))?; + + ensure!( + >::get(&H256::from(hash)).is_some(), + "Transaction does not have access to smart contract outputs" + ); + + >::remove(&H256::from(hash)); } Destination::ScriptHash(_hash) => { let witness = input.witness.clone(); @@ -1371,10 +1384,11 @@ impl crate::Pallet { // } } -fn coin_picker(outpoints: &Vec) -> Result, DispatchError> { +fn construct_inputs( + outpoints: &Vec, +) -> Result, DispatchError> { let mut inputs: Vec = Vec::new(); - // consensus-critical sorting function... let mut outpoints = outpoints.clone(); outpoints.sort(); @@ -1385,8 +1399,13 @@ fn coin_picker(outpoints: &Vec) -> Result inputs.push(TransactionInput::new_script( *outpoint, Builder::new().into_script(), - Builder::new().push_int(0x1337).into_script(), + Builder::new().push_slice(&outpoint.encode()).into_script(), )); + + // save the outpoint hash of the input UTXO to ECTL + // from which it can be fetched for validation when + // the node receives a TX that tries to spend OP_CALLs + >::insert(outpoint, true); } _ => { return Err(DispatchError::Other("Only CallPP vouts can be spent!")); @@ -1430,7 +1449,7 @@ where staking::withdraw::(stash_account_caller.clone()) } - fn send_conscrit_p2pk( + fn submit_c2pk_tx( caller: &T::AccountId, dest: &T::AccountId, value: u128, @@ -1439,39 +1458,30 @@ where let pubkey_raw: [u8; 32] = dest.encode().try_into().map_err(|_| "Failed to get caller's public key")?; - spend::( - caller, - &Transaction { - inputs: coin_picker::(outpoints)?, - outputs: vec![TransactionOutput::new_pubkey(value, H256::from(pubkey_raw))], - time_lock: Default::default(), - }, - ) - .map_err(|_| "Failed to spend the transaction!")?; + let tx = Transaction { + inputs: construct_inputs::(outpoints)?, + outputs: vec![TransactionOutput::new_pubkey(value, H256::from(pubkey_raw))], + time_lock: Default::default(), + }; + + spend::(caller, &tx).map_err(|_| "Failed to spend the transaction!")?; Ok(()) } - fn send_conscrit_c2c( + fn submit_c2c_tx( caller: &Self::AccountId, dest: &Self::AccountId, value: u128, data: &Vec, outpoints: &Vec, ) -> Result<(), DispatchError> { - spend::( - caller, - &Transaction { - inputs: coin_picker::(outpoints)?, - outputs: vec![TransactionOutput::new_call_pp( - value, - dest.clone(), - true, - data.clone(), - )], - time_lock: Default::default(), - }, - ) - .map_err(|_| "Failed to spend the transaction!")?; + let tx = Transaction { + inputs: construct_inputs::(outpoints)?, + outputs: vec![TransactionOutput::new_call_pp(value, dest.clone(), true, data.clone())], + time_lock: Default::default(), + }; + + spend::(caller, &tx).map_err(|_| "Failed to spend the transaction!")?; Ok(()) } } diff --git a/traits/utxo-api/src/lib.rs b/traits/utxo-api/src/lib.rs index e345ae9..b2de034 100644 --- a/traits/utxo-api/src/lib.rs +++ b/traits/utxo-api/src/lib.rs @@ -39,14 +39,14 @@ pub trait UtxoApi { fn withdraw_stake(stash_account_caller: &Self::AccountId) -> DispatchResultWithPostInfo; - fn send_conscrit_p2pk( + fn submit_c2pk_tx( caller: &Self::AccountId, destination: &Self::AccountId, value: u128, outpoints: &Vec, ) -> Result<(), DispatchError>; - fn send_conscrit_c2c( + fn submit_c2c_tx( caller: &Self::AccountId, destination: &Self::AccountId, value: u128, From 1699cfb7c1ac86e9c67c35027546448b6185c2dc Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Tue, 19 Oct 2021 10:52:55 +0300 Subject: [PATCH 14/41] utxo: Return Result from Utxo::create()/call() --- pallets/utxo/src/lib.rs | 64 +++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index 0bc1c2e..065216d 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -565,42 +565,36 @@ pub mod pallet { } pub fn create( - _caller: &T::AccountId, - _code: &Vec, - _utxo_hash: H256, - _utxo_value: u128, - _data: &Vec, - ) { - // let weight: Weight = 6000000000; - - // match T::ProgrammablePool::create(caller, weight, code, utxo_hash, utxo_value, data) { - // Ok(_) => log::info!("success!"), - // Err(e) => log::error!("failure: {:#?}", e), - // } + caller: &T::AccountId, + code: &Vec, + utxo_hash: H256, + utxo_value: u128, + data: &Vec, + ) -> Result<(), &'static str> { + let weight: Weight = 6000000000; + + T::ProgrammablePool::create(caller, weight, code, utxo_hash, utxo_value, data) } pub fn call( - _caller: &T::AccountId, - _dest: &T::AccountId, - _utxo_hash: H256, - _utxo_value: u128, - _fund_contract: bool, - _data: &Vec, - ) { - // let weight: Weight = 6000000000; - - // match T::ProgrammablePool::call( - // caller, - // dest, - // weight, - // utxo_hash, - // utxo_value, - // fund_contract, - // data, - // ) { - // Ok(_) => log::info!("success!"), - // Err(e) => log::error!("failure: {:#?}", e), - // } + caller: &T::AccountId, + dest: &T::AccountId, + utxo_hash: H256, + utxo_value: u128, + fund_contract: bool, + data: &Vec, + ) -> Result<(), &'static str> { + let weight: Weight = 6000000000; + + T::ProgrammablePool::call( + caller, + dest, + weight, + utxo_hash, + utxo_value, + fund_contract, + data, + ) } pub fn validate_transaction( @@ -1152,12 +1146,12 @@ pub mod pallet { Destination::CreatePP(script, data) => { log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); >::insert(hash, output); - create::(caller, script, hash, output.value, &data); + create::(caller, script, hash, output.value, &data)?; } Destination::CallPP(acct_id, fund, data) => { log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); >::insert(hash, output); - call::(caller, acct_id, hash, output.value, *fund, data); + call::(caller, acct_id, hash, output.value, *fund, data)?; } Destination::LockForStaking { .. } => { staking::lock_for_staking::(hash, output)?; From 889a1f093b5c9236b43e22283b407d58b78739e8 Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Wed, 20 Oct 2021 13:04:00 +0300 Subject: [PATCH 15/41] tests: Implement get_pubkey() for DestCallPP This allows the test framework to query UTXOs of the smart contract. --- test/functional/test_framework/mintlayer/utxo.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/functional/test_framework/mintlayer/utxo.py b/test/functional/test_framework/mintlayer/utxo.py index 20b512a..d95e5b3 100644 --- a/test/functional/test_framework/mintlayer/utxo.py +++ b/test/functional/test_framework/mintlayer/utxo.py @@ -3,6 +3,7 @@ import substrateinterface from substrateinterface import SubstrateInterface, Keypair from substrateinterface.exceptions import SubstrateRequestException +from substrateinterface.utils.ss58 import ss58_decode import scalecodec import os import logging @@ -50,7 +51,10 @@ def utxos(self, storage_name): """ Get UTXOs for given key """ def utxos_for(self, keypair): - matching = lambda e: e[1].destination.get_pubkey() == keypair.public_key + if type(keypair) == str: + matching = lambda e: e[1].destination.get_pubkey() == keypair + else: + matching = lambda e: e[1].destination.get_pubkey() == keypair.public_key return filter(matching, self.utxos('UtxoStore')) """ Get UTXOs for given key """ @@ -212,11 +216,14 @@ def __init__(self, dest_account, fund, input_data): @staticmethod def load(obj): - return DestCallPP(obj['dest_account'], obj['fund'], obj['input_data']) + return DestCallPP(ss58_decode(obj['dest_account']), obj['fund'], obj['input_data']) def json(self): return { 'CallPP': { 'dest_account': self.acct, 'fund': self.fund, 'input_data': self.data } } + def get_pubkey(self): + return str(self.acct) + class DestLockForStaking(Destination): def __init__(self, stash_account, controller_account, session_key): self.stash = stash_account @@ -248,7 +255,6 @@ def json(self): def get_ss58_address(self): return self.stash - class Output(): def __init__(self, value, destination, data): self.value = value From 9012d3b6b4e736f31ba9b6de20ace2a8e187278e Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Thu, 21 Oct 2021 16:39:03 +0300 Subject: [PATCH 16/41] pools: Prevent contracts from calling themselves Prevent a contract from calling itself through the ChainExtension as the smart contract already has capabilities of calling its own functions even without the existence of the ChainExtension, makes it harder to handle contract balances correctly and makes it very easy for the user to shoot themselves in the foot by accidentally calling the function that called into the ChainExtension, effectively resulting in infite recursion (bounded by the amount of gas, obviously). If this is found to be needed, the issue can be revisited. --- pallets/pp/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/pp/src/lib.rs b/pallets/pp/src/lib.rs index d8e6e31..f00bf4c 100644 --- a/pallets/pp/src/lib.rs +++ b/pallets/pp/src/lib.rs @@ -306,6 +306,10 @@ impl ChainExtension for Pallet< return Err(DispatchError::Other("Destination doesn't exist")); } + if acc_id == dest { + return Err(DispatchError::Other("Contract cannot call itself")); + } + // append data to the selector so the final data // passed on to the contract is in correct format let mut selector = selector.to_vec(); From c1a4b9467a4246c867e0166401fdaba1596880dc Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Thu, 21 Oct 2021 16:43:28 +0300 Subject: [PATCH 17/41] pools: Update smart contract balance after UTXO transfer If the UTXO transfer from contract to P2PK or contract succeeds, zero the balance of the smart contract as the contract no longer holds any funds. --- pallets/pp/src/lib.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pallets/pp/src/lib.rs b/pallets/pp/src/lib.rs index f00bf4c..c92495f 100644 --- a/pallets/pp/src/lib.rs +++ b/pallets/pp/src/lib.rs @@ -144,7 +144,12 @@ fn send_p2pk_tx( ensure!(fund_info.funds >= value, "Caller doesn't have enough funds"); let outpoints = fund_info.utxos.iter().map(|x| x.0).collect::>(); - T::Utxo::submit_c2pk_tx(caller, dest, value, &outpoints) + T::Utxo::submit_c2pk_tx(caller, dest, value, &outpoints).map(|_| { + >::mutate(&caller, |info| { + info.as_mut().unwrap().utxos = Vec::new(); + info.as_mut().unwrap().funds = 0; + }); + }) } /// Create Contract-to-Contract transfer that allows smart contracts to @@ -169,7 +174,12 @@ fn send_c2c_tx( ))?; let outpoints = fund_info.utxos.iter().map(|x| x.0).collect::>(); - T::Utxo::submit_c2c_tx(caller, dest, fund_info.funds, data, &outpoints) + T::Utxo::submit_c2c_tx(caller, dest, fund_info.funds, data, &outpoints).map(|_| { + >::mutate(&caller, |info| { + info.as_mut().unwrap().utxos = Vec::new(); + info.as_mut().unwrap().funds = 0; + }); + }) } impl ProgrammablePoolApi for Pallet From 661f654f544007ffb39f4d66a98e130f5500ffc6 Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Wed, 20 Oct 2021 15:35:38 +0300 Subject: [PATCH 18/41] tests: Implement functional tests for programmable pools Implement functional tests for PP and create assets directory which contains all smart contract related files --- .../{metadata.json => assets/c2c_tester.json} | 60 ++-- test/functional/assets/c2c_tester.wasm | Bin 0 -> 2118 bytes test/functional/assets/pooltester.json | 216 ++++++++++++ test/functional/assets/pooltester.wasm | Bin 0 -> 6455 bytes test/functional/code.wasm | Bin 2378 -> 0 bytes .../functional/feature_smart_contract_test.py | 318 +++++++++++++++--- test/functional/test_runner.py | 4 +- 7 files changed, 502 insertions(+), 96 deletions(-) rename test/functional/{metadata.json => assets/c2c_tester.json} (56%) create mode 100644 test/functional/assets/c2c_tester.wasm create mode 100644 test/functional/assets/pooltester.json create mode 100644 test/functional/assets/pooltester.wasm delete mode 100644 test/functional/code.wasm diff --git a/test/functional/metadata.json b/test/functional/assets/c2c_tester.json similarity index 56% rename from test/functional/metadata.json rename to test/functional/assets/c2c_tester.json index cf24b11..bde799b 100644 --- a/test/functional/metadata.json +++ b/test/functional/assets/c2c_tester.json @@ -1,46 +1,22 @@ { "metadataVersion": "0.1.0", "source": { - "hash": "0xd2d4276c5864e736fe51fd70c2e76ae600520dbead60bf3b8054f6d3d13e3dd7", + "hash": "0x241e86037e1803891112031bb03b7816d4b00d50effc920dae39c2004efb2ca4", "language": "ink! 3.0.0-rc4", "compiler": "rustc 1.56.0-nightly" }, "contract": { - "name": "pooltest", + "name": "c2c_tester", "version": "0.1.0", "authors": [ - "[your_name] <[your_email]>" + "RBB S.r.l" ] }, "spec": { "constructors": [ - { - "args": [ - { - "name": "init_value", - "type": { - "displayName": [ - "bool" - ], - "type": 1 - } - } - ], - "docs": [ - "Constructor that initializes the `bool` value to the given `init_value`." - ], - "name": [ - "new" - ], - "selector": "0x9bae9d5e" - }, { "args": [], - "docs": [ - "Constructor that initializes the `bool` value to `false`.", - "", - "Constructors can delegate to other constructors." - ], + "docs": [], "name": [ "default" ], @@ -51,25 +27,29 @@ "events": [], "messages": [ { - "args": [], - "docs": [ - " A message that can be called on instantiated contracts.", - " This one flips the value of the stored `bool` from `true`", - " to `false` and vice versa." + "args": [ + { + "name": "value", + "type": { + "displayName": [ + "u32" + ], + "type": 1 + } + } ], + "docs": [], "mutates": true, "name": [ - "flip" + "set_value" ], "payable": false, "returnType": null, - "selector": "0x633aa551" + "selector": "0xc6298215" }, { "args": [], - "docs": [ - " Simply returns the current value of our `bool`." - ], + "docs": [], "mutates": false, "name": [ "get" @@ -77,7 +57,7 @@ "payable": false, "returnType": { "displayName": [ - "bool" + "u32" ], "type": 1 }, @@ -103,7 +83,7 @@ "types": [ { "def": { - "primitive": "bool" + "primitive": "u32" } } ] diff --git a/test/functional/assets/c2c_tester.wasm b/test/functional/assets/c2c_tester.wasm new file mode 100644 index 0000000000000000000000000000000000000000..764fe2b6aca67ae3c36aab67dccf395ffcb9b6cd GIT binary patch literal 2118 zcma)7-D)I76h7xvbx)g~Nh;t<2#D3~3KMW~#3c!PH8l`NFBU}vFO$KYUF`gAI+MVH zWJX<if<)L!;w;CL(@6+mY#% zcFa`YJmqN|;zM{aKf{Z5ES9Gv&pAzw$D_TT)A#nH@nrjWa#)WZjmf;aaQw;wHw$k? zkJHiK$#{EGj}DF>j_Z2-fT&qsh|Av`JU*FRneu3=$CH!#fY^)=PSgGI{$c$@Se#kx zi0B0pW5uSeG()UmHnVwaS<(mN$9soQsAIn#?d{Rh!mo>+Kbd*@E`@$o@@?`QeB~*u zRBMJdjLuhd?r!Ez=h^jqfNfpRo}xnrhpcibKC>CG%Uuj!%@ra^I$*gAo(0W^PUC20ug?P_&PUvRpfIWNyP29e=; z-8vi%7P1ZFg4VFBTt4{j-^2*S~}3Zpa4Oe6gF+i;ro;tLO?RgQ zz1~FCr;eRAyXm%4k_ef-3U)#bBiTjEVd{Mhl6Cy##Y6UQGqb#(c$wdGT1AA01J>!xYb zHsYaErypHwCzVE8U0!>1^_273@~Ly(hnF9IWc8E5hbp^ycb*B9Y$~{1jlyl|WSwc6 zW#|^b5-(iH%c&be_r&t)laGAT-IPlJw|n{rdv^cI2S%K~)y0?G%D~^|Y9IHGYZqt7 zbD3Mr^gYyfweel+yO?}0F>GJ$YiAQ?nJ_!93SJj3_=WIZ8sEQ=FN9G`IjnC!UU1{e z^~GScQu|>zTx|)X^sjbXb1WTP=}K1ibJ=JyJIu&p+)SJnvAA^ij89|cvWlLU-1s(E z9`o6GNmKU`YRNBVbqyDjZx9|IKyVNkPz*BY;en@&SR`UK2oSS|SS2D_*|^1Qwsz&6 z-mimLwK50*7LY0$gJEFVVaO{n46MHhApjjhy$_s&aBgP_wjzK^TA--5-JOqYRzL6g za>@6zl4Wv6@yaB0v(y)T;y)TU{4t0?ZwwY_@;xz_X7N2SSPs7u|y#HvLYWYY7&FUtdo1q+BUSp3Xh_$eg27hn0nZ zzd_8$>V#2xF;tdjdD!xfwJfn4^ukF0aI+B1fuiqwJgqDle+fHm!}0d{ux{qWOTv*s zF7tR1+yGuxf0vdv<~c1$Fh`=iJQ2crw%OFuCll%bXD?VYK32)*k`GmdPQRN>pUd=y znt1?H$%vgzPH@q~#R3I*gMo)lXb1Eo|l2b$F_FPc0Rn{S0`$HY#dg zy`>KGuIR%~jV=3~S{|N>hlR%Hvd~ScOgtLb$X7AA1IdvLCrv_p(^$~Q+sDYA7T5~1 zQ0~~EOyfIH?wnBSu*ctgz;Blm&%3wocRSUJw~qHXcfeQn@%Hf^&j;R3&pT6(BI2&` z9B!!A{CMchO7U&up~q0ffV-tXg=I2;n|C`U5L)i>+Yd;~n2+nJ1HWu0AduhxKgh!*IL+QGc#_Y5)MH)kp=aVrvjq73&Te@B-(ZOR|L~3oD z5g7VTh6PR!Y71dDS+`6`S|m?n90abb9py)T)+9M1I*jD_g^(Q&h|2pghedH-cPu3I zP~Ibi0rAibLCts;YXYSsAIvNjDz;qta@P9q6$+1%Mm8xtt_jy+Q+V3%w(zvyKXb{} z#h9Q}tdq_c!aiPZPGiV4L{)H$;obNx_YemutL0c&DkXDkAC%lKb*Mn zr1Zs5?Pv#HyOwFF7K&jtjjC=`?{zGVwOGdNI({ZPYE(BeLHghUE~)VV#mv@lM`n-^ ziTkXgesbpf1r*CIgu~%{{Oc6f5(uixv(=1}LZ;k#crgR4Mk=y7X{y?~q=c{}3F8$N zT^aRl*C&7)ll0^Pv6o;~AtL|2Ycedy>X!A%P(6u)V8{rODU}jA@b{`MeA~<&IC!-y zUh`*!)DstS*}c5FBTS{zdNEikl;M;E1CXZFW;>C;c?@K>zpB<%Kdx?uC5iDWTY}3N zub(l+$IJBGFeok!I^-!}E0(V5@0L#w3MD_1V6imITRjzTVjvdhBo;Hi>JI^2C;r_Z zLi62+RuNtT|I!_jQ_%XFKeW0!o%}WnVql_pp#1yfLVOm0L7>GBa{KhDV*HR?dX;SV+bXbIh z=vTIO*l6b+ZQuUusAec;r@~DQNC+1R;7MY*2uiLh)0xDPi{-;tRS$Y3pq`RzKhVpj z-g>oRw@k{&lnKR82SS|Znesxb=^ zCZ(!UPH*1ZCOAB&ZQ?RbQ!hDb(yp3P&eAj;ProFpqFzK>v|A}HRir&x}( zqf`P^2hLh%lP}L@v;8f4Rn=DLzd=Ue)=Dp!xVS=VU}a(GxWa_;j8SlfmQrt7%w95) z=d$}#dQSV7HD;Q)r_EZgG@BPQ>X$Wvi5R(2ffEMjt6^AUWJ2Jm3v&HCezavkkt6E<&AE~NCF5khS2W4KQ(5H2;*1Dq)s>cs@t;d)wi* z`R*s1D3{C?npEsC1#A27S;Y|0-}QY0iWLtEfk&kI7I#PXjHmT1j$pKkcb;?%c z@FB=0H7Sra6<~`g7Pu=KYBO!-M&0<_@4xYDDSQ$8>@?@TVl0x4l`&O5TEo7902E8P zbIb8G?V+v&-YO3r4&2=;TElRMd{t@Y8OhAsWao(geZFN8rre@|mwj^cXZ!#!)OiSJ z4my0hd#wK9)FeC``Y^2HsoCIis?~TSU&^B?aqSLG;z}rYd zZuYmEVciru+JAB0?h4$Cq1j%$@(Z3T2@>+&HX)nC-qvR0nubw^=B*Ai2fiYnNmEM3 zLtK^A%*B}Qe=KGDstO~!3cJKclP>PNNb`8pXQ<Ej(vKp{y;uE~MD}zb9$XUV=qjahnEVr)^`{Z5#M}Jvwef<~dU|*9GZ- zYV)7=980YrdhLSQ-w|kDU~w}9Uzyde1UT=+%SNiRBNl!585d@?Kf>8f)pWpd^xTLU zas40!4q<0Fxw13oqgvC!2{a!+)2xQ4kpVb0o3Jg>*d{95IbGUNQ}C!-S5Ly|pPBE< zWa(lojO4+<#Ok~_Pp@!|1r5n~91(9aF>Z_>DnEPm9oan~^MYV;&+fiwjldkcK7*OV zi-?8oyjT&Rt+b2DtSswYaf_2Ge6C}>&PA-`KEM*X$s2SNAA(Ns>BbH5wByWTTAlc@ zJ2<;pkmyr7Q{`3)(ySuQ>cKc*yX!20A+f|RS*$fj=e6b#;FzQH@691&nK?R*Ic&*b z=HOPrsay*)BWJskMVgLq_e|hORCnvCs*eJWJFF6EeMwHqxZdjsNv>ByZZ}+tNVPdp zMPC}Y(i411y6F&V2UMkBkCij-Il1E%Sh`x2!508o0^KWs>pZ>ix|IRuRc*yHU>`2YX_ literal 0 HcmV?d00001 diff --git a/test/functional/code.wasm b/test/functional/code.wasm deleted file mode 100644 index d3c74df855ead838d7d7b7045c66aba336fb3423..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2378 zcma)8&x>3|5U#53_vR&UCfgB}Wf!sEbA=7KIAX{ydvSVTckv*45IklVGX~%O%FfOP z0Gs_KRIP7j%g_`PCR zrc>JGDNlFJRF~AQ@M3H&gh}CC9&?_Zj`j}vPJeIT9ZhbZPL9WWcSmGjJ~(~pK+?gR z(c=Ez!P)5cWW0BD`qgMW9^D~I$5-R=*GKoxCNohPMdQ)rYn1njHVPmtr0EBRHVav{S$?uHr9UMQPYxc{%g9CC`e{t7-W#;KS6b4PrE95!& z##2~qZWvlOx?j}2yWBh77xVoPYO9_-MS}tcMdNb3E-v_%+{EI?3wulYGs%YTvDs)G zReFEF@w_SubYizQiK+_ zzCtuZUN_$3{uo#--_#I5SGPrNAeYadM`4kKt`-PQD|AV?*y^pDMNj1iDN}C07Pu7v z#WAf4E?pJtdax1Pz7ByPV=Gh+Sv0q>NV$atIe=QDD~f%MiV9w%Zjiu10EV}h18!W# z9elD}es{@F@(O;Gl$IY+e)NX<9{eZ=vwh+RURr)MZ15u>AW@{jMv87?LCTb10aj2S zUKAaEtV60L4NB=K$Y`HP+L!U_b3i ztk;%cM0m0l)j+y{lQnAHa8pT_%QA2!fv^gMDlkL;_*oe{LQBWXb5GBQ#iNJi5(4z* z6WoPGLM=e8aNnRnZb5q@$V9umK{m^ua>){eek?qVTVS2SiGA6f<+yFw*Wf~ttQ!|h z8-(lD`Li0Vo%F=#!x#GR?QiQ`1BFUHT*O;X9NrcVOepTh{xsnS)&@151=n?py8{=|8Kc!0kKTyau zB8*-lER@uV9R!9KF8>pqZP6LnW;1kIcwfO~;cp#Z|rH{0le-iSf6@jv;qzt*Io-BER9}cUHmLOr}=c~opN{?Iu227`EbtM#< zyjL@AjW?-CMNc|YAuP~oT>$KAQLPbGjB~$V$8(IG+4!K;t*=8B-HXYogHb;jp54`e zyH?RA_~|-JrB+vj7tD4oJz7$zUY0Ls`YWK--ns+|{$2$rB8l`PH)_z=`TUf{q)wUR zitV8TCXOIPpI|!OM^p|&TH!d?(M3B}^^;k1;)xO`^E?EHsU!A$h^Yg8SE1ZtR(kXB zvn?-U^d$xprWE*Hp;$?m-K=f?1!q%rX>04{<2utu#+K^5>KF_pZ{p|Bn+CJC(|{`- zXz1LgGCFAxCk!z{fdJY$h=up8{BZt3i=U_HB65zqk)m$n3=A;NyTGc+L(0l`SyN)m Vx0ne3&mb~*ES?Ncj;DjC_!s7WchUd= diff --git a/test/functional/feature_smart_contract_test.py b/test/functional/feature_smart_contract_test.py index d155788..487c841 100755 --- a/test/functional/feature_smart_contract_test.py +++ b/test/functional/feature_smart_contract_test.py @@ -3,9 +3,9 @@ # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Smart contract test -""" +""" Smart contract test """ + # Imports should be in PEP8 ordering (std library first, then third party # libraries then local imports). from collections import defaultdict @@ -23,6 +23,23 @@ import os +# helper function to reduce code duplication +def submit_pp_tx(client, input_utxo, alice, value, output): + tx = utxo.Transaction( + client, + inputs=[ + utxo.Input(input_utxo.outpoint(0)), + ], + outputs=[ + utxo.Output( + value=value, + destination=utxo.DestPubkey(alice.public_key), + data=None, + ), + output + ] + ).sign(alice, [input_utxo.outputs[0]], [0]) + return tx, client.submit(alice, tx) class ExampleTest(MintlayerTestFramework): # Each functional test is a subclass of the MintlayerTestFramework class. @@ -56,97 +73,290 @@ def setup_network(self): # sync_all() should not include node2, since we're not expecting it to # sync. connect_nodes(self.nodes[0], self.nodes[1]) - # self.sync_all([self.nodes[0:1]]) def run_test(self): """Main test logic""" client = self.nodes[0].rpc_client - substrate = client.substrate - alice = Keypair.create_from_uri('//Alice') + bob = Keypair.create_from_uri('//Erin') - # Find a suitable UTXO initial_utxo = [x for x in client.utxos_for(alice) if x[1].value >= 50][0] + value = initial_utxo[1].json()["value"] + + self.log.error(initial_utxo) - tx0 = utxo.Transaction( + tx = utxo.Transaction( client, inputs=[ utxo.Input(initial_utxo[0]), ], outputs=[ utxo.Output( - value=50, + value=value, destination=utxo.DestPubkey(alice.public_key), - data=None + data=None, ), - utxo.Output( - value=10, - destination=utxo.DestCreatePP( - code=os.path.join(os.path.dirname(__file__), "code.wasm"), - data=[0xed, 0x4b, 0x9d, 0x1b], # default() constructor selector - ), - data=None - ), - # This output prevent reward overflow - utxo.Output( - value=3981553255926290448385, # = genesis amount - u64::MAX - destination=utxo.DestPubkey(alice.public_key), - data=None - ) ] ).sign(alice, [initial_utxo[1]]) + client.submit(alice, tx) - # submit transaction and get the extrinsic and block hashes - (ext, blk,_) = client.submit(alice, tx0) + # invalid bytecode + value -= 1 - # each new smart contract instantiation creates a new account - # fetch this SS58-formatted account address and return it - # and the hex-encoded account id - (ss58, acc_id) = contract.getContractAddresses(substrate, blk) + (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value=1, + destination=utxo.DestCreatePP( + code=[0x00], + data=[0xed, 0x4b, 0x9d, 0x1b], + ), + data=None, + )) + assert_equal(contract.getContractAddresses(substrate, blk), None) + + # invalid value + (invalid_tx, res) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value=0, + destination=utxo.DestCreatePP( + code=os.path.join(os.path.dirname(__file__), "assets/pooltester.wasm"), + data=[0xed, 0x4b, 0x9d, 0x1b], + ), + data=None, + )) + assert_equal(res, None) - # create new contract instance which can be used to interact - # with the instantiated contract + # valid data + value -= 1 + + (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value=1, + destination=utxo.DestCreatePP( + code=os.path.join(os.path.dirname(__file__), "assets/pooltester.wasm"), + data=[0xed, 0x4b, 0x9d, 0x1b], + ), + data=None, + )) + + (ss58, acc_id) = contract.getContractAddresses(substrate, blk) contractInstance = contract.ContractInstance( ss58, - os.path.join(os.path.dirname(__file__), "metadata.json"), + os.path.join(os.path.dirname(__file__), "assets/pooltester.json"), substrate ) - # read the value of the flipper contract + # verify the initial state of the smart contract result = contractInstance.read(alice, "get") - assert_equal(result.contract_result_data.value, False) + assert_equal(result.contract_result_data.value, 1337) + # valid contract call + value -= 1 msg_data = contractInstance.generate_message_data("flip", {}) - self.log.info("Contract msg_data: {}, {}, {}".format(ss58, acc_id, msg_data)) - tx1 = utxo.Transaction( + (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value=1, + destination=utxo.DestCallPP( + dest_account=acc_id, + fund=False, + input_data=bytes.fromhex(msg_data.to_hex()[2:]), + ), + data=None, + )) + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1337) + + # invalid `value` given + msg_data = contractInstance.generate_message_data("flip", {}) + + (invalid_tx, res) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value=0, + destination=utxo.DestCallPP( + dest_account=alice.public_key, + fund=False, + input_data=bytes.fromhex(msg_data.to_hex()[2:]), + ), + data=None, + )) + assert_equal(res, None) + + # test contract-to-p2k transfer from alice to bob + # + # `send_to_pubkey()` first funds the smart contract from alice's funds + # and when the wasm code is executed, the funds are transferred to bob + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": bob.public_key }) + value -= 555 + + (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value = 555, + destination = utxo.DestCallPP( + dest_account = acc_id, + fund = True, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ), + data = None, + )) + + # verify that bob actually received the utxo + bobs_utxos = [x for x in client.utxos_for(bob)] + assert_equal(len(bobs_utxos), 1) + assert_equal(bobs_utxos[0][1].json()['value'], 555) + + # test contract-to-p2pk again but this time don't fund the contract + # meaning that after the TX, bob only has the UTXO he received in the previous test case + # and the contract has a UTXO with value 666 + msg_data = contractInstance.generate_message_data("send_to_pubkey", { "dest": bob.public_key }) + value -= 666 + + (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value = 666, + destination = utxo.DestCallPP( + dest_account = acc_id, + fund = False, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ), + data = None, + )) + + # verify that bob still has the same amount of UTXOs + utxos = [x for x in client.utxos_for(bob)] + assert_equal(len(utxos), 1) + + # verify that the contract has one utxo with value 666 + utxos = [x for x in client.utxos_for(acc_id[2:])] + assert_equal(len(utxos), 1) + assert_equal(utxos[0][1].json()["value"], 666) + + # try to call a contract that doesn't exist (alice's public key + # doesn't point to a valid smart contract) + # + # TODO: because we don't have gas refunding, the money is still + # spent, i.e., if the UTXO set is queried, you'll find a UTXO + # with value 888 meaning user just lost his money which is + # not the correct behavior but the implementation is still under way + msg_data = contractInstance.generate_message_data("fund", {}) + value -= 888 + + (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value = 888, + destination = utxo.DestCallPP( + dest_account = alice.public_key, + fund = True, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ), + data = None, + )) + + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, -1337) + + # Test cross-contract calls + # + # First instantiate another smart contract and verify it has + # been created correctly by querying its value. + # + # Then call the `set_value()` method of newly instantiated contract + # indirectly by creating a UTXO that calls the pooltester's + # `call_contract()` method which dispatches the call to `set_value()` + # + # When all that's done, query the value again and verify that it has been updated + value -= 111 + + (tx, (_, blk, _)) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value = 111, + destination = utxo.DestCreatePP( + code = os.path.join(os.path.dirname(__file__), "assets/c2c_tester.wasm"), + data = [0xed, 0x4b, 0x9d, 0x1b], + ), + data = None, + )) + + (ss58_c2c, acc_id_c2c) = contract.getContractAddresses(substrate, blk) + c2cInstance = contract.ContractInstance( + ss58_c2c, + os.path.join(os.path.dirname(__file__), "assets/c2c_tester.json"), + substrate + ) + + # verify the initial state of the smart contract + result = c2cInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, 555) + + msg_data = contractInstance.generate_message_data("call_contract", { + "dest": acc_id_c2c, + "selector": "0xc6298215", + "value": 999, + }) + value -= 222 + + (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value = 222, + destination = utxo.DestCallPP( + dest_account = acc_id, + fund = True, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ), + data = None, + )) + + # verify that the call succeeded + result = c2cInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, 999) + + # Try to spend the funds of a contract + # + # First fund the contract with some amount of UTXO, + # verify that the fund worked (updated state variable) + # and then try to spend those funds and verify that the + # spend is rejected by the local PP node because the + # smart contract has not spent them and thus the outpoint + # hash is not in the local storage + # + # NOTE: spending the DestCallPP UTXOs doesn't require signatures + # but instead the outpoint hash of the UTXO. This is queried + # from the runtime storage as the smart contract has not transferred + # these funds, the outpoint hash is **not** found from the storage + # and this TX is rejected as invalid + msg_data = contractInstance.generate_message_data("fund", {}) + value -= 555 + + self.log.info("here") + self.log.error(tx) + + (tx, _) = submit_pp_tx(client, tx, alice, value, utxo.Output( + value = 555, + destination = utxo.DestCallPP( + dest_account = acc_id, + fund = True, + input_data = bytes.fromhex(msg_data.to_hex()[2:]), + ), + data = None, + )) + + result = contractInstance.read(alice, "get") + assert_equal(result.contract_result_data.value, 1338) + + utxos = [x for x in client.utxos_for(acc_id[2:])] + assert_equal(len(utxos), 1) + assert_equal(utxos[0][1].json()["value"], 555) + + invalid_tx = utxo.Transaction( client, - inputs=[ - utxo.Input(tx0.outpoint(0)), + inputs = [ + utxo.Input(utxos[0][0]), ], outputs=[ utxo.Output( - value=49, + value=555, destination=utxo.DestPubkey(alice.public_key), - data=None - ), - utxo.Output( - value=1, - destination=utxo.DestCallPP( - dest_account=acc_id, - fund=False, - input_data=bytes.fromhex(msg_data.to_hex()[2:]), - ), - data=None + data=None, ), ] - ).sign(alice, [tx0.outputs[0]], [0]) - (ext_hash, blk_hash,_) = client.submit(alice, tx1) - - result = contractInstance.read(alice, "get") - assert_equal(result.contract_result_data.value, True) + ) + # size of the outpoint (32 bytes, 0x10) + the outpoint itself + # the outpoint in the witness field is valid but because the + # smart contract has not spent the funds, the TX is rejected + tx.inputs[0].witness = bytearray.fromhex("10" + str(utxos[0][0])[2:]) + assert_equal(client.submit(alice, invalid_tx), None) if __name__ == '__main__': ExampleTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index c4dd2ed..ad07c29 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -64,9 +64,9 @@ 'feature_staking_diff_addresses.py', 'feature_staking_unlock_not_validator.py', 'feature_staking_withdraw_no_unlock.py', - 'feature_staking_withdraw_not_validator.py' + 'feature_staking_withdraw_not_validator.py', + 'feature_smart_contract_test.py', # 'feature_staking_unlock_and_withdraw.py' ## should be ran on 20 secs - # 'feature_smart_contract_test.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] From 63007472e40eeedb7f9de62dce003c530e79a85a Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Sun, 21 Nov 2021 13:08:29 +0200 Subject: [PATCH 19/41] Add Dockerfile for mintlayer-core --- Dockerfile | 23 +++++++++++++++++++++++ docs/README.md | 15 +++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9d24952 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM debian:bullseye + +ENV REQUIRED_PACKAGES git clang curl libssl-dev llvm libudev-dev + +RUN apt-get update \ + && apt-get install -y $REQUIRED_PACKAGES \ + && curl https://sh.rustup.rs -sSf | sh -s -- -y \ + && $HOME/.cargo/bin/rustup default stable \ + && $HOME/.cargo/bin/rustup update \ + && $HOME/.cargo/bin/rustup update nightly \ + && $HOME/.cargo/bin/rustup target add wasm32-unknown-unknown --toolchain nightly + +RUN apt-get update \ + && apt-get install -y clang-9 \ + && git clone https://github.com/mintlayer/core \ + && cd core \ + && $HOME/.cargo/bin/cargo build --release + +WORKDIR /core + +EXPOSE 30333 + +CMD RUST_LOG=info target/release/mintlayer-core --base-path /tmp/ml-core --chain=assets/Testnet1Spec.json diff --git a/docs/README.md b/docs/README.md index 20ee934..dc2b9b1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -113,6 +113,21 @@ Let's look at these flags in detail: | `--node-key ` | The Ed25519 secret key to use for `libp2p` networking. The value is parsed as a hex-encoded Ed25519 32 byte secret key, i.e. 64 hex characters. WARNING: Secrets provided as command-line arguments are easily exposed. Use of this option should be limited to development and testing. | | `--telemetry-url` | Tells the node to send telemetry data to a particular server. The one we've chosen here is hosted by Parity and is available for anyone to use. You may also host your own (beyond the scope of this article) or omit this flag entirely. | | `--validator` | Means that we want to participate in block production and finalization rather than just sync the network. | + +## Docker setup + +Alternatively, Docker can be used to launch a Mintlayer node. In the root directory, run: + +``` +docker build -t mintlayer-core . +docker run -t mintlayer-core +``` + +If you want to save the blockchain to host, run: +``` +docker run -v ~/ml-blockchain:/tmp/ml-core -t mintlayer-core +``` + ## Create a chain specification In the preceding example, we used `--chain local` which is a predefined "chain spec" that has Alice and Bob specified as validators along with many other useful defaults. From 80863e25085226135eb86f7ec7116e9ea2a23ff7 Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 17 Nov 2021 14:23:52 +0700 Subject: [PATCH 20/41] Wait for withdrawal era instead of waiting 500 seconds --- test/functional/feature_staking_unlock_and_withdraw.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_staking_unlock_and_withdraw.py b/test/functional/feature_staking_unlock_and_withdraw.py index c467002..79a8f51 100755 --- a/test/functional/feature_staking_unlock_and_withdraw.py +++ b/test/functional/feature_staking_unlock_and_withdraw.py @@ -65,6 +65,7 @@ def run_test(self): assert_equal(len(ledger[0][1]['unlocking']),0) alice_stash = Keypair.create_from_uri('//Alice//stash') + alice = Keypair.create_from_uri('//Alice') # fetch the genesis utxo from storage utxos = list(client.utxos_for(alice_stash)) @@ -87,9 +88,11 @@ def run_test(self): assert_equal(events[2].value['event_id'], 'StakeUnlocked') ledger = list(client.get_staking_ledger()) + assert_equal(len(ledger),2) - time.sleep(500) + while client.current_era() < client.withdrawal_era(alice): + time.sleep(1) (_, _, w_events) = client.withdraw_stake(alice_stash) From dbcb7abd2f024c415f25e93e3c8469af38de8582 Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 17 Nov 2021 14:24:19 +0700 Subject: [PATCH 21/41] Enable feature_staking_unlock_and_withdraw test --- test/functional/test_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ad07c29..635b752 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -66,7 +66,7 @@ 'feature_staking_withdraw_no_unlock.py', 'feature_staking_withdraw_not_validator.py', 'feature_smart_contract_test.py', -# 'feature_staking_unlock_and_withdraw.py' ## should be ran on 20 secs + 'feature_staking_unlock_and_withdraw.py' ## should be ran on 20 secs # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] From ed8f677fa479d1d476b8f5b7b337d26c0d959a82 Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 17 Nov 2021 17:33:25 +0700 Subject: [PATCH 22/41] Change current_era to return an int rather than scalecodec.types.U32 --- test/functional/test_framework/mintlayer/staking.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/functional/test_framework/mintlayer/staking.py b/test/functional/test_framework/mintlayer/staking.py index 722e9ac..cdfec7a 100644 --- a/test/functional/test_framework/mintlayer/staking.py +++ b/test/functional/test_framework/mintlayer/staking.py @@ -20,7 +20,10 @@ def current_era(self): module='Staking', storage_function='CurrentEra' ) - return query + # this query returns an object of type scalecodec.types.U32 + # so we convert it to an integer + # TODO find a more elegant way to do conversion + return int("{}".format(query)) """ gets the staking ledger of the given key """ def get_ledger(self, keypair): From f5c1ac2f105e7083c3f7969cdce160b50911bff3 Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 17 Nov 2021 17:32:46 +0700 Subject: [PATCH 23/41] Added hard timeout for awaiting withdrawal_era --- test/functional/feature_staking_unlock_and_withdraw.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_staking_unlock_and_withdraw.py b/test/functional/feature_staking_unlock_and_withdraw.py index 79a8f51..7edff73 100755 --- a/test/functional/feature_staking_unlock_and_withdraw.py +++ b/test/functional/feature_staking_unlock_and_withdraw.py @@ -91,8 +91,14 @@ def run_test(self): assert_equal(len(ledger),2) + time_elapsed = 0 + max_wait = 1200 + time_step = 1 while client.current_era() < client.withdrawal_era(alice): - time.sleep(1) + if time_elapsed > max_wait: + raise Exception('Timeout: waited too long for withdrawal_era') + time.sleep(time_step) + time_elapsed +=time_step (_, _, w_events) = client.withdraw_stake(alice_stash) From 1152a6d7241e5bad79a19312631cd42136a6b3bb Mon Sep 17 00:00:00 2001 From: b-yap <2826165+b-yap@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:56:51 +0800 Subject: [PATCH 24/41] staking functional tests: expect exception on testing failed transactions --- .../functional/feature_staking_extra_not_validator.py | 3 ++- .../feature_staking_extra_wrong_controller.py | 3 ++- test/functional/feature_staking_less_than_minimum.py | 11 +++++++++-- test/functional/test_framework/mintlayer/utxo.py | 1 + test/functional/test_framework/util.py | 11 +++++++++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/test/functional/feature_staking_extra_not_validator.py b/test/functional/feature_staking_extra_not_validator.py index 7aa0b9c..8907822 100755 --- a/test/functional/feature_staking_extra_not_validator.py +++ b/test/functional/feature_staking_extra_not_validator.py @@ -14,6 +14,7 @@ from test_framework.test_framework import MintlayerTestFramework from test_framework.util import ( assert_equal, + assert_raises_substrate_exception, connect_nodes, wait_until, ) @@ -90,7 +91,7 @@ def run_test(self): ), ] ).sign(charlie_stash, [utxos[0][1]]) - client.submit(charlie_stash, tx1) + assert_raises_substrate_exception(client.submit, charlie_stash, tx1) new_count = list(client.staking_count()) # there should only be 2 count of staking, which are Alice and Bob diff --git a/test/functional/feature_staking_extra_wrong_controller.py b/test/functional/feature_staking_extra_wrong_controller.py index b46d52e..9cdbdcb 100755 --- a/test/functional/feature_staking_extra_wrong_controller.py +++ b/test/functional/feature_staking_extra_wrong_controller.py @@ -13,6 +13,7 @@ from test_framework.test_framework import MintlayerTestFramework from test_framework.util import ( assert_equal, + assert_raises_substrate_exception, connect_nodes, wait_until, ) @@ -86,7 +87,7 @@ def run_test(self): ] ).sign(alice_stash, [utxos[0][1]]) - client.submit(alice_stash, tx1) + assert_raises_substrate_exception(client.submit, alice_stash, tx1) # Get Bob's stash new_count = list(client.get_staking_count(alice_stash))[0][1] diff --git a/test/functional/feature_staking_less_than_minimum.py b/test/functional/feature_staking_less_than_minimum.py index e23d78f..de2457d 100755 --- a/test/functional/feature_staking_less_than_minimum.py +++ b/test/functional/feature_staking_less_than_minimum.py @@ -13,6 +13,7 @@ from test_framework.test_framework import MintlayerTestFramework from test_framework.util import ( assert_equal, + assert_raises_substrate_exception, connect_nodes, wait_until, ) @@ -69,6 +70,7 @@ def run_test(self): # there's only 2 record of staking, which are alice and bob. assert_equal( len(list(client.staking_count())), 2 ) + print("hoy hoy hoy: ", utxos[0][1].json()) tx1 = utxo.Transaction( client, inputs=[ @@ -80,6 +82,11 @@ def run_test(self): destination=utxo.DestPubkey(charlie_stash.public_key), data=None ), + utxo.Output( + value=39999949950 * COIN, + destination=utxo.DestPubkey(alice.public_key), + data=None + ), ] ).sign(alice, [utxos[0][1]]) client.submit(alice, tx1) @@ -101,8 +108,8 @@ def run_test(self): data=None ), ] - ).sign(charlie_stash, tx1.outputs) - client.submit(charlie_stash, tx2) + ).sign(charlie_stash, [tx1.outputs[0]]) + assert_raises_substrate_exception(client.submit, charlie_stash, tx2) # there should only be 2 still, because Charlie failed on the staking. assert_equal(len(list(client.staking_count())), 2 ) diff --git a/test/functional/test_framework/mintlayer/utxo.py b/test/functional/test_framework/mintlayer/utxo.py index d95e5b3..a4c1a61 100644 --- a/test/functional/test_framework/mintlayer/utxo.py +++ b/test/functional/test_framework/mintlayer/utxo.py @@ -111,6 +111,7 @@ def submit(self, keypair, tx): return (receipt.extrinsic_hash, receipt.block_hash, receipt.triggered_events) except SubstrateRequestException as e: self.log.debug("Failed to send: {}".format(e)) + raise e """ Submit a transaction onto the blockchain: unlock """ def unlock_request_for_withdrawal(self, keypair): diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 840cdbc..ef52085 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -5,6 +5,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Helpful routines for regression testing.""" +from substrateinterface.exceptions import SubstrateRequestException from base64 import b64encode from binascii import hexlify, unhexlify from decimal import Decimal, ROUND_DOWN @@ -68,6 +69,16 @@ def assert_raises_message(exc, message, fun, *args, **kwds): else: raise AssertionError("No exception raised") +def assert_raises_substrate_exception(fun, *args, **kwds): + try: + fun(*args, **kwds) + except SubstrateRequestException as e: + return True + else: + raise AssertionError("No SubstrateRequestException raised") + + + def assert_raises_process_error(returncode, output, fun, *args, **kwds): """Execute a process and asserts the process return code and output. From f8769ccae8c1e01aa88957054fa3cd5c487fe4e2 Mon Sep 17 00:00:00 2001 From: b-yap <2826165+b-yap@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:05:50 +0800 Subject: [PATCH 25/41] remove prints and extra new lines --- test/functional/feature_staking_less_than_minimum.py | 1 - test/functional/test_framework/util.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/test/functional/feature_staking_less_than_minimum.py b/test/functional/feature_staking_less_than_minimum.py index de2457d..c928c35 100755 --- a/test/functional/feature_staking_less_than_minimum.py +++ b/test/functional/feature_staking_less_than_minimum.py @@ -70,7 +70,6 @@ def run_test(self): # there's only 2 record of staking, which are alice and bob. assert_equal( len(list(client.staking_count())), 2 ) - print("hoy hoy hoy: ", utxos[0][1].json()) tx1 = utxo.Transaction( client, inputs=[ diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index ef52085..2d5ce34 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -77,8 +77,6 @@ def assert_raises_substrate_exception(fun, *args, **kwds): else: raise AssertionError("No SubstrateRequestException raised") - - def assert_raises_process_error(returncode, output, fun, *args, **kwds): """Execute a process and asserts the process return code and output. From 5cc65bb7241856d503d2a57d6263de8a1ff17721 Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Fri, 12 Nov 2021 15:50:57 +0700 Subject: [PATCH 26/41] Updated docs for node start-up, tokens, and transactions --- docs/README.md | 65 +++++++++++++++++++++++-------- docs/tokens.md | 93 +++++++++++++++++++++++---------------------- docs/transaction.md | 30 +++++++-------- 3 files changed, 113 insertions(+), 75 deletions(-) diff --git a/docs/README.md b/docs/README.md index dc2b9b1..1afc7fc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,9 +3,21 @@ ## Quick start Binaries can be found [here](https://github.com/mintlayer/core/releases). + +Running a node also requires as input a chain specification. +Currently a single chain specification for the testnet is provided, and can be downloaded using curl: + +``` +curl --proto '=https' -sSf \ + https://raw.githubusercontent.com/mintlayer/core/master/assets/Testnet1Spec.json \ + --output Testnet1Spec.json +``` Download and run: ``` -mintlayer-core --base-path data/my_first_ml_node --validator --rpc-external --rpc-methods Unsafe --chain=Testnet1Spec.json +mintlayer-core \ + --base-path data/my_first_ml_node \ + --validator \ + --chain=Testnet1Spec.json ``` to start a node. It will automatically connect to the Mintlayer bootnodes. @@ -80,7 +92,7 @@ See [Mintlayer installation on Windows](windows_installation.md) ## Running a node Clone the repository: -```bash +``` git clone https://github.com/mintlayer/core.git ``` @@ -94,25 +106,47 @@ to build the project. Finally, to run a node: ``` -RUST_LOG=info ./target/release/mintlayer-core --base-path [PATH_TO_DB] --name [NODE_NAME] --port [P2P_PORT] --ws-port [WEB_SOCKET_PORT] --rpc-port [RPC_PORT] --validator --rpc-methods Unsafe --chain=[CHAIN_SPEC] +RUST_LOG=info ./target/release/mintlayer-core \ + --base-path [PATH_TO_DB] \ + --name [NODE_NAME] \ + --port [P2P_PORT] \ + --ws-port [WEB_SOCKET_PORT] \ + --rpc-port [RPC_PORT] \ + --validator \ + --chain=[CHAIN_SPEC] ``` + For example, ``` -RUST_LOG=info ./target/release/mintlayer-core --base-path data/node1 --name brian --port 30333 --ws-port 9945 --rpc-port 9933 --validator --rpc-methods Unsafe --chain=Testnet1Spec.json +RUST_LOG=info ./target/release/mintlayer-core \ + --base-path data/node1 \ + --name brian \ + --port 30333 \ + --ws-port 9945 \ + --rpc-port 9933 \ + --validator \ + --chain=Testnet1Spec.json ``` + Let's look at these flags in detail: -|
Flags
| Descriptions | -| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--base-path` | Specifies a directory where Mintlayer should store all the data related to this chain. If the directory does not exist, it will be created for you. If other blockchain data already exists there you will get an error. Either clear the directory or choose a different one. | -| `--chain local` | Specifies which chain specification to use. There are a few prepackaged options including `local`, `development`, and `staging` but generally one specifies their own chain spec file. We'll specify our own file in a later step. | -| `--alice` | Puts the predefined Alice keys (both for block production and finalization) in the node's keystore. Generally one should generate their own keys and insert them with an RPC call. We'll generate our own keys in a later step. This flag also makes Alice a validator. | -| `--port 30333` | Specifies the port that your node will listen for p2p traffic on. `30333` is the default and this flag can be omitted if you're happy with the default. If Bob's node will run on the same physical system, you will need to explicitly specify a different port for it. | -| `--ws-port 9945` | Specifies the port that your node will listen for incoming WebSocket traffic on. The default value is `9944`. This example uses a custom web socket port number (`9945`). | -| `--rpc-port 9933` | Specifies the port that your node will listen for incoming RPC traffic on. `9933` is the default, so this parameter may be omitted. | -| `--node-key ` | The Ed25519 secret key to use for `libp2p` networking. The value is parsed as a hex-encoded Ed25519 32 byte secret key, i.e. 64 hex characters. WARNING: Secrets provided as command-line arguments are easily exposed. Use of this option should be limited to development and testing. | -| `--telemetry-url` | Tells the node to send telemetry data to a particular server. The one we've chosen here is hosted by Parity and is available for anyone to use. You may also host your own (beyond the scope of this article) or omit this flag entirely. | -| `--validator` | Means that we want to participate in block production and finalization rather than just sync the network. | +|
Flags
| Descriptions | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--base-path` | Specifies a directory where Mintlayer should store all the data related to this chain. If the directory does not exist, it will be created for you. If other blockchain data already exists there you will get an error. Either clear the directory or choose a different one. | +| `--chain=[CHAIN_SPEC_FILE]` | Specifies which chain specification to use. A chain specification, or "chain spec", is a collection of configuration information that dictates which network a blockchain node will connect to, which entities it will initially communicate with, and what consensus-critical state it must have at genesis. | +| `--alice` | Puts the predefined Alice keys (both for block production and finalization) in the node's keystore. Generally one should generate their own keys and insert them with an RPC call. We'll generate our own keys in a later step. This flag also makes Alice a validator. | +| `--port 30333` | Specifies the port that your node will listen for p2p traffic on. `30333` is the default and this flag can be omitted if you're happy with the default. If Bob's node will run on the same physical system, you will need to explicitly specify a different port for it. | +| `--ws-port 9945` | Specifies the port that your node will listen for incoming WebSocket traffic on. The default value is `9944`. This example uses a custom web socket port number (`9945`). | +| `--rpc-port 9933` | Specifies the port that your node will listen for incoming RPC traffic on. `9933` is the default, so this parameter may be omitted. | +| `--telemetry-url` | Tells the node to send telemetry data to a particular server. The one we've chosen here is hosted by Parity and is available for anyone to use. You may also host your own (beyond the scope of this article) or omit this flag entirely. | +| `--validator` | Means that we want to participate in block production and finalization rather than just sync the network. | + +*Note*: As a safety precaution, the node will listen to RPC interfaces on localhost only. +It is possible expose the node's RPC port publicly `--rpc-external`, but only do this if you understand the risks involved. As a safer alternative to enable RPC calls from outside the node, consider using tunnels. + +**TODO** Provide an explanation/examples of methods that are unsafe to call. Or link to a list, together with an explanation of why each method is unsafe. + +*Note*: Some RPC calls can be used to control the node's behavior and should never (or rarely) be exposed. We call such methods _Unsafe_, and they are disabled by default. It is possible to enable them using `--rpc-methods Unsafe`. ## Docker setup @@ -128,6 +162,7 @@ If you want to save the blockchain to host, run: docker run -v ~/ml-blockchain:/tmp/ml-core -t mintlayer-core ``` + ## Create a chain specification In the preceding example, we used `--chain local` which is a predefined "chain spec" that has Alice and Bob specified as validators along with many other useful defaults. diff --git a/docs/tokens.md b/docs/tokens.md index a0d349b..b66a396 100644 --- a/docs/tokens.md +++ b/docs/tokens.md @@ -1,25 +1,23 @@ # Mintlayer Tokens -**TODO Do we want Rust code in this doc?** -Each transaction output must carry a data field. This field describes the purpose of the transaction. +This document describes the structure of transactions involving tokens on Mintlayer. Currently, two types of tokens are supported: -We must highlight the following at the moment: +1. *MLS-01 tokens*: MLS-01 is the "basic" Mintlayer token standard, analogous to, say, ERC-20 tokens on Ethereum. -- Transfer Tokens or NFT -- Token Issuance -- Burning tokens -- NFT creation +2. NFTs + +A transaction involving transaction output carries a (possibly empty) `data` field specifying the purpose of the transaction. -All transactions must be signed on the client side. This means we cannot sign transactions on the node side. -**TODO what is the connection between these two sentences?** -In the future, there will be changes to the structures of transactions and we will be required to track them. -**TODO by structures above to we mean rust structs? Why is this interesting to the user?** +The data field can be any of the following: + +- Transfer of MLS-01 tokens or NFTs +- Token issuance +- Token burning +- NFT creation -## Transfer Tokens +## Transferring Tokens -**TODO sentence fragment, I don't understand...** -**TODO When do we NOT use the TxData field?** -For transfering funds to another person in a given UTXO. To send MLT we will use the MLT token ID, which is equal to 0. If the token ID is equal to the ID of the MLS-01 (**TODO what is MLS-01**) token, then the amount of token is transferred to the recipient. The commission is taken only in MLT (**TODO what is this commission**. If the token ID is equal to the ID of any NFT, then the data of this NFT is transferred to the recipient without changing the creator field. The UTXO model itself allows to determine the owner of the NFT. +To send MLT we use the MLT token ID, which is equal to 0. If the token ID is equal to the ID of an MLS-01 token, then the amount of the token is transferred to the recipient. The transaction fee is taken only in MLT. If the token ID is equal to the ID of any NFT, then the data of this NFT is transferred to the recipient without changing the creator field. The UTXO model itself allows to determine the owner of the NFT. ```rust TxData { @@ -30,27 +28,32 @@ TxData { } ``` -## Issue Tokens -When issuing a new token, we specify the data for creating a new token in the transaction input, where the `token_id` is a hash of the inputs. **TODO which inputs?** -**TODO explain remaining fields** +## Issuing Tokens -**TODO understand the comment** -```rust +To issue a new token, we specify the data for creating the token in the transaction output's `data` field: + + ```rust TxData { TokenIssuanceV1 { - token_id: TokenID, - token_ticker: Vec, - amount_to_issue: Value, - // Should be not more than 18 numbers - number_of_decimals: u8, - metadata_URI: Vec, - } + token_ticker: Vec, + amount_to_issue: Value, + // Should not be more than 18 + number_of_decimals: u8, + metadata_URI: Vec, + } } -``` + ``` + +Here, `token_ticker` is a short name given to the token (up to 5 chararcters long). + +The `metatdata_URI` is a web link to a JSON file where we can store additional information about the token + +The _token ID_ is defined as a hash of the _first input_ of the issuance transaction. + + +### Burning Tokens -### Burn Tokens -**TODO verify - the input should be a utxo that contains tokens, the output should contain the TokenBurn arm** -A token burning - as an input is used by UTXO that contains tokens. As an output, the data field should contain the TokenBurn arm. If the amount in burning the output is less than in the input then there should exist at least one output for returning the funds change. In this case, you can burn any existing number of tokens. After this operation, you can use UTXO for the remaining amount of tokens. +The input for a token-burning transaction should be a UTXO containing tokens. In the output, the data field should contain the _TokenBurn_ variant. If the `amount_to_burn` in the output is less than the amount in the input, then there should exist at least one output for returning the difference. In this way, any existing number of tokens can be burned. ```rust TxData { @@ -61,18 +64,17 @@ TxData { } ``` ### NFT -TO DO +**TODO** ## Wallet +**TODO** -TO DO -## Issue and Transfer Tokens +## Issuing and Transferring Tokens -**TODO who are these examples meant for?** ```rust /* Transfer and Issuance in one Tx */ -// Alice issues 1_000_000_000 MLS-01, and send them to Karl +// Alice issues 1_000_000_000 MLS-01 tokens, and sends them to Karl let (utxo0, input0) = tx_input_gen_no_signature(); let tx = Transaction { inputs: vec![input0], @@ -96,13 +98,17 @@ let tx = Transaction { } .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); -let first_issuance_token_id = TokenId::new(&tx.inputs[0]); assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + +let first_issuance_token_id = TokenId::new(&tx.inputs[0]); + +// The newly issued token is represented by the TransactionOutput at index 1 +// "Outoint" here refers to the hash of the TransactionOutput struct. let token_utxo_hash = tx.outpoint(1); let token_utxo = tx.outputs[1].clone(); -// Let's send 300_000_000 and rest back and create another token +// Let's send alice 300_000_000 and the rest back to Karl, andalso create another token, "KarlToken" let tx = Transaction { inputs: vec![TransactionInput::new_empty(token_utxo_hash)], outputs: vec![ @@ -126,7 +132,7 @@ let tx = Transaction { 0, H256::from(karl_pub_key), OutputData::TokenIssuanceV1 { - token_ticker: "Token".as_bytes().to_vec(), + token_ticker: "KarlToken".as_bytes().to_vec(), amount_to_issue: 5_000_000_000, // Should be not more than 18 numbers number_of_decimals: 12, @@ -137,10 +143,13 @@ let tx = Transaction { time_lock: Default::default(), } .sign_unchecked(&[token_utxo.clone()], 0, &karl_pub_key); + assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + let alice_transfer_utxo_hash = tx.outpoint(0); let karl_transfer_utxo_hash = tx.outpoint(1); let karl_issuance_utxo_hash = tx.outpoint(2); + assert!(!UtxoStore::::contains_key(H256::from( token_utxo_hash ))); @@ -148,8 +157,6 @@ assert!(UtxoStore::::contains_key(alice_transfer_utxo_hash)); assert!(UtxoStore::::contains_key(karl_transfer_utxo_hash)); assert!(UtxoStore::::contains_key(karl_issuance_utxo_hash)); - - // Let's check token transfer UtxoStore::::get(alice_transfer_utxo_hash) .unwrap() @@ -165,8 +172,6 @@ UtxoStore::::get(alice_transfer_utxo_hash) }) .unwrap(); - - UtxoStore::::get(karl_transfer_utxo_hash) .unwrap() .data @@ -181,8 +186,6 @@ UtxoStore::::get(karl_transfer_utxo_hash) }) .unwrap(); - - // Let's check token issuance UtxoStore::::get(karl_issuance_utxo_hash) .unwrap() diff --git a/docs/transaction.md b/docs/transaction.md index 164b0af..1da057d 100644 --- a/docs/transaction.md +++ b/docs/transaction.md @@ -2,17 +2,11 @@ ## UTXO overview -**TODO maybe system/model instead of structure?** -Mintlayer uses the Bitcoin UTXO structure instead of the account-based models of Ethereum, Ripple, Stellar, and others. -**TODO need explanation of this sentence, it is not clear to me** -Since each transaction's output is stored separately (even when sent to a single address), it is only possible to spend the entire transaction’s output. +Mintlayer uses the a UTXO system similar to Bitcoin's, instead of the account-based models of Ethereum, Ripple, Stellar, and others. There are three essential reasons for this: -There are three essential reasons for choosing the UTXO model: - -- It is compatible with technologies already implemented in Bitcoin, such atomic swaps and the Lightning Network. +- The utxo model is compatible with technologies already implemented in Bitcoin, such atomic swaps and the Lightning Network. -**TODO - why does this improve privacy?** -- It is more privacy-oriented: a single wallet usually utilizes multiple addresses, making it difficult and sometimes impossible to determine which addresses belong to whichs user. +- The utxo model is more privacy-oriented: a single wallet can utilize multiple addresses, making it difficult and sometimes impossible to determine which addresses belong to which user. - Payments can be batched together (aggregated) in a single transaction, saving a considerable amount of the space otherwise required for making a single transaction per payment. @@ -24,10 +18,10 @@ There are three destination types for transaction outputs : A general Mintlayer transaction looks something like this: -**TODO Not sure we want this in Rust code. Too developer specific. Not clear what H256 is, witness, lock** -**TODO possibly add a link to information about the utxo system** -**TODO if we go for the rust struct, then we need the data field. Also, what is this field?** +**TODO if we go for the rust struct, then we need the data field in output** + **TODO timelock is not a string..."** + ```rust Transaction { inputs: [ @@ -66,17 +60,23 @@ In Mintlayer, as Substrate, transanctions need to be signed before being submitt - The timelock **TODO Explain what we are showing here** + **TODO We need to document the python mintlayer crate** + **TODO what is utxos[0][0]? Utxos is a two-dimentsional array?** + **TODO I want to see the Transaction python class. What is the utxo[0][1] in the signature?** -**In the second transaction's signature, outpoints instead of outputs** + +**TODO In the second transaction's signature, outpoints instead of outputs** ### Python ```python from substrateinterface import Keypair import mintlayer.utxo as utxo -client = self.nodes[0].rpc_client +#... + +account = Account(args) alice = Keypair.create_from_uri('//Alice') bob = Keypair.create_from_uri('//Bob') @@ -85,7 +85,7 @@ bob = Keypair.create_from_uri('//Bob') utxos = list(client.utxos_for(alice)) tx1 = utxo.Transaction( - client, + account.client, inputs=[ utxo.Input(utxos[0][0]), ], From 445eec4d6b3532291122ff4e32418264b4cdd7a0 Mon Sep 17 00:00:00 2001 From: b-yap <2826165+b-yap@users.noreply.github.com> Date: Tue, 23 Nov 2021 13:53:09 +0800 Subject: [PATCH 27/41] assert_raise: fix alice_bob_test on tx fees exceeding u64::MAX --- test/functional/feature_alice_bob_test.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/test/functional/feature_alice_bob_test.py b/test/functional/feature_alice_bob_test.py index 1de62a7..b13d26c 100755 --- a/test/functional/feature_alice_bob_test.py +++ b/test/functional/feature_alice_bob_test.py @@ -17,6 +17,7 @@ connect_nodes, wait_until, ) +from test_framework.messages import COIN class ExampleTest(MintlayerTestFramework): @@ -65,6 +66,8 @@ def run_test(self): # fetch the genesis utxo from storage utxos = list(client.utxos_for(alice)) + print("how are you: ",utxos[0][1].json()) + tx1 = utxo.Transaction( client, inputs=[ @@ -72,10 +75,15 @@ def run_test(self): ], outputs=[ utxo.Output( - value=50, + value=50 * COIN, destination=utxo.DestPubkey(bob.public_key), data=None ), + utxo.Output( + value=39999999940 * COIN, + destination=utxo.DestPubkey(alice.public_key), + data=None + ) ] ).sign(alice, [utxos[0][1]]) client.submit(alice, tx1) @@ -87,17 +95,17 @@ def run_test(self): ], outputs=[ utxo.Output( - value=30, + value=30 * COIN, destination=utxo.DestPubkey(alice.public_key), data=None ), utxo.Output( - value=20, + value=15 * COIN, destination=utxo.DestPubkey(bob.public_key), data=None ), ] - ).sign(bob, tx1.outputs) + ).sign(bob, [tx1.outputs[0]]) client.submit(bob, tx2) From 7c6fb95c547209b7a41f1c55d55f6d2f79ccaf16 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Fri, 19 Nov 2021 11:45:39 +0000 Subject: [PATCH 28/41] global: Replace mentions of node template with Mintlayer terminology --- Cargo.lock | 96 ++++++++++++++++++------------------- README.md | 4 +- doc/rust-setup.md | 2 +- docker-compose.yml | 10 ++-- node/Cargo.toml | 4 +- node/src/chain_spec.rs | 4 +- node/src/command.rs | 4 +- node/src/main.rs | 2 +- node/src/rpc.rs | 2 +- node/src/service.rs | 6 +-- pallets/utxo/README.md | 4 +- pallets/utxo/src/weights.rs | 2 +- runtime/Cargo.toml | 4 +- scripts/docker_run.sh | 4 +- 14 files changed, 74 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e84a3ad..fda3f99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3402,7 +3402,7 @@ dependencies = [ "frame-benchmarking-cli", "jsonrpc-core", "log", - "node-template-runtime", + "mintlayer-runtime", "pallet-contracts", "pallet-contracts-rpc", "pallet-transaction-payment-rpc", @@ -3446,6 +3446,53 @@ dependencies = [ "ureq", ] +[[package]] +name = "mintlayer-runtime" +version = "3.0.0" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "hex-literal 0.3.3", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-contracts", + "pallet-contracts-primitives", + "pallet-contracts-rpc-runtime-api", + "pallet-grandpa", + "pallet-pp", + "pallet-randomness-collective-flip", + "pallet-session", + "pallet-staking", + "pallet-sudo", + "pallet-template", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-utxo", + "pallet-utxo-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", + "sp-transaction-pool", + "sp-version", + "substrate-wasm-builder", +] + [[package]] name = "mio" version = "0.6.23" @@ -3659,53 +3706,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "node-template-runtime" -version = "3.0.0" -dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", - "frame-executive", - "frame-support", - "frame-system", - "frame-system-benchmarking", - "frame-system-rpc-runtime-api", - "hex-literal 0.3.3", - "log", - "pallet-aura", - "pallet-authorship", - "pallet-balances", - "pallet-contracts", - "pallet-contracts-primitives", - "pallet-contracts-rpc-runtime-api", - "pallet-grandpa", - "pallet-pp", - "pallet-randomness-collective-flip", - "pallet-session", - "pallet-staking", - "pallet-sudo", - "pallet-template", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-utxo", - "pallet-utxo-rpc-runtime-api", - "parity-scale-codec", - "sp-api", - "sp-block-builder", - "sp-consensus-aura", - "sp-core", - "sp-inherents", - "sp-offchain", - "sp-runtime", - "sp-session", - "sp-staking", - "sp-std", - "sp-transaction-pool", - "sp-version", - "substrate-wasm-builder", -] - [[package]] name = "nodrop" version = "0.1.14" diff --git a/README.md b/README.md index 4cfeb56..ef4383d 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ RUST_LOG=debug RUST_BACKTRACE=1 ./target/release/mintlayer-core -lruntime=debug ### Connect with Polkadot-JS Apps Front-end -Once the node template is running locally, you can connect it with **Polkadot-JS Apps** front-end -to interact with your chain. [Click here](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) connecting the Apps to your local node template. +Once the node is running locally, you can connect it with **Polkadot-JS Apps** front-end +to interact with your chain. [Click here](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) connecting the Apps to your local node. ### Connect with Mintlayer's UI TODO diff --git a/doc/rust-setup.md b/doc/rust-setup.md index 34f6e43..3a61968 100644 --- a/doc/rust-setup.md +++ b/doc/rust-setup.md @@ -3,7 +3,7 @@ title: Installation --- This page will guide you through the steps needed to prepare a computer for development with the -Substrate Node Template. Since Substrate is built with +Substrate Node. Since Substrate is built with [the Rust programming language](https://www.rust-lang.org/), the first thing you will need to do is prepare the computer for Rust development - these steps will vary based on the computer's operating system. Once Rust is configured, you will use its toolchains to interact with Rust projects; the diff --git a/docker-compose.yml b/docker-compose.yml index cfc4437..36bcddb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,16 +2,16 @@ version: "3.2" services: dev: - container_name: node-template + container_name: mintlayer-core image: paritytech/ci-linux:974ba3ac-20201006 - working_dir: /var/www/node-template + working_dir: /var/www/mintlayer-core ports: - "9944:9944" environment: - - CARGO_HOME=/var/www/node-template/.cargo + - CARGO_HOME=/var/www/mintlayer-core/.cargo volumes: - - .:/var/www/node-template + - .:/var/www/mintlayer-core - type: bind source: ./.local target: /root/.local - command: bash -c "cargo build --release && ./target/release/node-template --dev --ws-external" + command: bash -c "cargo build --release && ./target/release/mintlayer-core --dev --ws-external" diff --git a/node/Cargo.toml b/node/Cargo.toml index 0988d79..cc4e01e 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -21,7 +21,7 @@ substrate-build-script-utils = {version = '3.0.0', git = 'https://github.com/par [dependencies] jsonrpc-core = '18.0.0' structopt = '0.3.8' -node-template-runtime = {version = '3.0.0', path = '../runtime'} +mintlayer-runtime = {version = '3.0.0', path = '../runtime'} pallet-utxo-rpc = { path = "../pallets/utxo/rpc" } pallet-utxo-rpc-runtime-api = { path = "../pallets/utxo/rpc/runtime-api" } log = "0.4.8" @@ -227,4 +227,4 @@ branch = "master" [features] default = [] -runtime-benchmarks = ['node-template-runtime/runtime-benchmarks'] +runtime-benchmarks = ['mintlayer-runtime/runtime-benchmarks'] diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 1711f10..ec295a6 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -1,4 +1,4 @@ -use node_template_runtime::{ +use mintlayer_runtime::{ pallet_utxo, AccountId, BalancesConfig, GenesisConfig, PpConfig, SessionConfig, Signature, StakerStatus, StakingConfig, SudoConfig, SystemConfig, UtxoConfig, MINIMUM_STAKE, NUM_OF_VALIDATOR_SLOTS, WASM_BINARY, @@ -222,7 +222,7 @@ fn testnet_genesis( session_keys.push(( auth_keys.stash_account_id(), auth_keys.stash_account_id(), - node_template_runtime::opaque::SessionKeys { + mintlayer_runtime::opaque::SessionKeys { aura: AuraId::from(auth_keys.sr25519_public_controller), grandpa: GrandpaId::from(auth_keys.ed25519_public), }, diff --git a/node/src/command.rs b/node/src/command.rs index 4513b27..02afffd 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -18,7 +18,7 @@ use crate::chain_spec::MltKeysInfo; use crate::cli::{Cli, Subcommand}; use crate::{chain_spec, service}; -use node_template_runtime::{pallet_utxo, Block, TEST_NET_MLT_ORIG_SUPPLY}; +use mintlayer_runtime::{pallet_utxo, Block, TEST_NET_MLT_ORIG_SUPPLY}; use sc_cli::{ChainSpec, Role, RuntimeVersion, SubstrateCli}; use sc_network::config::MultiaddrWithPeerId; use sc_service::PartialComponents; @@ -155,7 +155,7 @@ impl SubstrateCli for Cli { } fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { - &node_template_runtime::VERSION + &mintlayer_runtime::VERSION } } diff --git a/node/src/main.rs b/node/src/main.rs index a4182cd..0f4fdaa 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -1,4 +1,4 @@ -//! Substrate Node Template CLI library. +//! Mintlayer Node CLI library. #![warn(missing_docs)] mod chain_spec; diff --git a/node/src/rpc.rs b/node/src/rpc.rs index e46071d..7a13715 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -7,7 +7,7 @@ use std::sync::Arc; -use node_template_runtime::{opaque::Block, AccountId, Balance, BlockNumber, Hash, Index}; +use mintlayer_runtime::{opaque::Block, AccountId, Balance, BlockNumber, Hash, Index}; pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; diff --git a/node/src/service.rs b/node/src/service.rs index 826dc56..fa8f0c1 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -1,4 +1,4 @@ -use node_template_runtime::{self, opaque::Block, RuntimeApi}; +use mintlayer_runtime::{self, opaque::Block, RuntimeApi}; use sc_client_api::{ExecutorProvider, RemoteBackend}; use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; pub use sc_executor::NativeElseWasmExecutor; @@ -17,11 +17,11 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { - node_template_runtime::api::dispatch(method, data) + mintlayer_runtime::api::dispatch(method, data) } fn native_version() -> sc_executor::NativeVersion { - node_template_runtime::native_version() + mintlayer_runtime::native_version() } } diff --git a/pallets/utxo/README.md b/pallets/utxo/README.md index b98bce9..f0763f4 100644 --- a/pallets/utxo/README.md +++ b/pallets/utxo/README.md @@ -216,7 +216,7 @@ Make sure the _Option_ input box is still empty, then click the **+** butt 4. In node's [chain_spec.rs](https://github.com/mintlayer/mintlayer-node/blob/master/node/src/chain_spec.rs): 4.1. Import the ff: ```rust - use node_template_runtime::{UtxoConfig, pallet_utxo}; + use mintlayer_runtime::{UtxoConfig, pallet_utxo}; use sp_core:H256; ``` 4.2. add one more param on function `testnet_genesis()`: @@ -259,7 +259,7 @@ Make sure the _Option_ input box is still empty, then click the **+** butt 6. Go back to the workspace directory `$> cd ..` and run: ```bash RUST_LOG=runtime=debug - target/release/node-template benchmark + target/release/mintlayer-core benchmark --chain dev --execution=wasm --wasm-execution=compiled diff --git a/pallets/utxo/src/weights.rs b/pallets/utxo/src/weights.rs index 47806f1..b3086aa 100644 --- a/pallets/utxo/src/weights.rs +++ b/pallets/utxo/src/weights.rs @@ -5,7 +5,7 @@ //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 // Executed Command: -// target/release/node-template +// target/release/mintlayer-core // benchmark // --chain // dev diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index f947e73..339b6cf 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -3,8 +3,8 @@ authors = ['Substrate DevHub '] edition = '2018' homepage = 'https://substrate.dev' license = 'Unlicense' -name = 'node-template-runtime' -repository = 'https://github.com/substrate-developer-hub/substrate-node-template/' +name = 'mintlayer-runtime' +repository = 'https://github.com/mintlayer/core' version = '3.0.0' [package.metadata.docs.rs] diff --git a/scripts/docker_run.sh b/scripts/docker_run.sh index 61e6b0f..51581ca 100755 --- a/scripts/docker_run.sh +++ b/scripts/docker_run.sh @@ -2,9 +2,9 @@ set -e -echo "*** Start Substrate node template ***" +echo "*** Start Mintlayer node ***" cd $(dirname ${BASH_SOURCE[0]})/.. docker-compose down --remove-orphans -docker-compose run --rm --service-ports dev $@ \ No newline at end of file +docker-compose run --rm --service-ports dev $@ From d8ef0db6cc537661914bbabb17a6120351c0522a Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Fri, 19 Nov 2021 11:52:42 +0000 Subject: [PATCH 29/41] runtime: Remove the template pallet --- Cargo.lock | 15 ---- pallets/template/Cargo.toml | 70 ------------------ pallets/template/README.md | 1 - pallets/template/src/benchmarking.rs | 20 ----- pallets/template/src/lib.rs | 106 --------------------------- pallets/template/src/mock.rs | 63 ---------------- pallets/template/src/tests.rs | 23 ------ runtime/Cargo.toml | 7 -- runtime/src/lib.rs | 11 --- 9 files changed, 316 deletions(-) delete mode 100644 pallets/template/Cargo.toml delete mode 100644 pallets/template/README.md delete mode 100644 pallets/template/src/benchmarking.rs delete mode 100644 pallets/template/src/lib.rs delete mode 100644 pallets/template/src/mock.rs delete mode 100644 pallets/template/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index fda3f99..f73b185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3471,7 +3471,6 @@ dependencies = [ "pallet-session", "pallet-staking", "pallet-sudo", - "pallet-template", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -4097,20 +4096,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-template" -version = "3.0.0" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "serde", - "sp-core", - "sp-io", - "sp-runtime", -] - [[package]] name = "pallet-timestamp" version = "4.0.0-dev" diff --git a/pallets/template/Cargo.toml b/pallets/template/Cargo.toml deleted file mode 100644 index 971f543..0000000 --- a/pallets/template/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -authors = ['Substrate DevHub '] -description = 'FRAME pallet template for defining custom runtime logic.' -edition = '2018' -homepage = 'https://substrate.dev' -license = 'Unlicense' -name = 'pallet-template' -readme = 'README.md' -repository = 'https://github.com/substrate-developer-hub/substrate-node-template/' -version = '3.0.0' - -[package.metadata.docs.rs] -targets = ['x86_64-unknown-linux-gnu'] - -[dependencies.codec] -default-features = false -features = ['derive'] -package = 'parity-scale-codec' -version = '2.0.0' - -[dependencies.frame-support] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -version = '4.0.0-dev' -branch = "master" - -[dependencies.frame-system] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -version = '4.0.0-dev' -branch = "master" - -[dependencies.frame-benchmarking] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -optional = true -version = '4.0.0-dev' -branch = "master" - -[dev-dependencies.serde] -version = '1.0.126' - -[dev-dependencies.sp-runtime] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -version = '4.0.0-dev' -branch = "master" - -[dev-dependencies.sp-core] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -version = '4.0.0-dev' -branch = "master" - -[dev-dependencies.sp-io] -default-features = false -git = 'https://github.com/paritytech/substrate.git' -version = '4.0.0-dev' -branch = "master" - -[features] -default = ['std'] -runtime-benchmarks = ['frame-benchmarking'] -std = [ - 'codec/std', - 'frame-support/std', - 'frame-system/std', - 'frame-benchmarking/std', -] -try-runtime = ['frame-support/try-runtime'] diff --git a/pallets/template/README.md b/pallets/template/README.md deleted file mode 100644 index 8d751a4..0000000 --- a/pallets/template/README.md +++ /dev/null @@ -1 +0,0 @@ -License: Unlicense \ No newline at end of file diff --git a/pallets/template/src/benchmarking.rs b/pallets/template/src/benchmarking.rs deleted file mode 100644 index 26cf63d..0000000 --- a/pallets/template/src/benchmarking.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Benchmarking setup for pallet-template - -use super::*; - -#[allow(unused)] -use crate::Pallet as Template; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_system::RawOrigin; - -benchmarks! { - do_something { - let s in 0 .. 100; - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), s) - verify { - assert_eq!(Something::::get(), Some(s)); - } -} - -impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs deleted file mode 100644 index 76344c0..0000000 --- a/pallets/template/src/lib.rs +++ /dev/null @@ -1,106 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -/// Edit this file to define custom logic or remove it if it is not needed. -/// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// -pub use pallet::*; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; - -#[frame_support::pallet] -pub mod pallet { - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; - use frame_system::pallet_prelude::*; - - /// Configure the pallet by specifying the parameters and types on which it depends. - #[pallet::config] - pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. - type Event: From> + IsType<::Event>; - } - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - - // The pallet's runtime storage items. - // https://substrate.dev/docs/en/knowledgebase/runtime/storage - #[pallet::storage] - #[pallet::getter(fn something)] - // Learn more about declaring storage items: - // https://substrate.dev/docs/en/knowledgebase/runtime/storage#declaring-storage-items - pub type Something = StorageValue<_, u32>; - - // Pallets use events to inform users when important changes are made. - // https://substrate.dev/docs/en/knowledgebase/runtime/events - #[pallet::event] - #[pallet::metadata(T::AccountId = "AccountId")] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Event documentation should end with an array that provides descriptive names for event - /// parameters. [something, who] - SomethingStored(u32, T::AccountId), - } - - // Errors inform users that something went wrong. - #[pallet::error] - pub enum Error { - /// Error names should be descriptive. - NoneValue, - /// Errors should have helpful documentation associated with them. - StorageOverflow, - } - - #[pallet::hooks] - impl Hooks> for Pallet {} - - // Dispatchable functions allows users to interact with the pallet and invoke state changes. - // These functions materialize as "extrinsics", which are often compared to transactions. - // Dispatchable functions must be annotated with a weight and must return a DispatchResult. - #[pallet::call] - impl Pallet { - /// An example dispatchable that takes a singles value as a parameter, writes the value to - /// storage and emits an event. This function must be dispatched by a signed extrinsic. - #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] - pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { - // Check that the extrinsic was signed and get the signer. - // This function will return an error if the extrinsic is not signed. - // https://substrate.dev/docs/en/knowledgebase/runtime/origin - let who = ensure_signed(origin)?; - - // Update storage. - >::put(something); - - // Emit an event. - Self::deposit_event(Event::SomethingStored(something, who)); - // Return a successful DispatchResultWithPostInfo - Ok(()) - } - - /// An example dispatchable that may throw a custom error. - #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] - pub fn cause_error(origin: OriginFor) -> DispatchResult { - let _who = ensure_signed(origin)?; - - // Read a value from storage. - match >::get() { - // Return an error if the value has not been set. - None => Err(Error::::NoneValue)?, - Some(old) => { - // Increment the value read from storage; will error in the event of overflow. - let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; - // Update the value in storage with the incremented result. - >::put(new); - Ok(()) - } - } - } - } -} diff --git a/pallets/template/src/mock.rs b/pallets/template/src/mock.rs deleted file mode 100644 index c3c0b5b..0000000 --- a/pallets/template/src/mock.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate as pallet_template; -use frame_support::parameter_types; -use frame_system as system; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, -}; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - -impl system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); -} - -impl pallet_template::Config for Test { - type Event = Event; -} - -// Build genesis storage according to the mock runtime. -pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default().build_storage::().unwrap().into() -} diff --git a/pallets/template/src/tests.rs b/pallets/template/src/tests.rs deleted file mode 100644 index 39c2180..0000000 --- a/pallets/template/src/tests.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok}; - -#[test] -fn it_works_for_default_value() { - new_test_ext().execute_with(|| { - // Dispatch a signed extrinsic. - assert_ok!(TemplateModule::do_something(Origin::signed(1), 42)); - // Read pallet storage and assert an expected result. - assert_eq!(TemplateModule::something(), Some(42)); - }); -} - -#[test] -fn correct_error_for_none_value() { - new_test_ext().execute_with(|| { - // Ensure the expected error is thrown when no value is present. - assert_noop!( - TemplateModule::cause_error(Origin::signed(1)), - Error::::NoneValue - ); - }); -} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 339b6cf..d843826 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -229,11 +229,6 @@ git = 'https://github.com/paritytech/substrate.git' version = '4.0.0-dev' branch = "master" -[dependencies.pallet-template] -default-features = false -version = '3.0.0' -path = '../pallets/template' - [dependencies.pallet-utxo] default-features = false path = "../pallets/utxo" @@ -256,7 +251,6 @@ runtime-benchmarks = [ 'frame-system/runtime-benchmarks', 'hex-literal', 'pallet-balances/runtime-benchmarks', - 'pallet-template/runtime-benchmarks', 'pallet-timestamp/runtime-benchmarks', 'pallet-utxo/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', @@ -275,7 +269,6 @@ std = [ 'pallet-session/std', 'pallet-staking/std', 'pallet-sudo/std', - 'pallet-template/std', 'pallet-timestamp/std', 'pallet-transaction-payment-rpc-runtime-api/std', 'pallet-transaction-payment/std', diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f9951df..4bf2451 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -47,9 +47,6 @@ use pallet_transaction_payment::CurrencyAdapter; pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Percent, Permill}; -/// Import the template pallet. -pub use pallet_template; - pub use pallet_pp; pub use pallet_utxo; use pallet_utxo::MLT_UNIT; @@ -310,11 +307,6 @@ impl pallet_sudo::Config for Runtime { type Call = Call; } -/// Configure the pallet-template in pallets/template. -impl pallet_template::Config for Runtime { - type Event = Event; -} - parameter_types! { pub const MinimumStake: u128 = MINIMUM_STAKE; pub const StakeWithdrawalFee: u128 = 1 * MLT_UNIT; @@ -511,8 +503,6 @@ construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, - // Include the custom logic from the pallet-template in the runtime. - TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, Utxo: pallet_utxo::{Pallet, Call, Config, Storage, Event}, Pp: pallet_pp::{Pallet, Call, Config, Storage, Event}, Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, @@ -772,7 +762,6 @@ impl_runtime_apis! { add_benchmark!(params, batches, frame_system, SystemBench::); add_benchmark!(params, batches, pallet_balances, Balances); add_benchmark!(params, batches, pallet_timestamp, Timestamp); - add_benchmark!(params, batches, pallet_template, TemplateModule); add_benchmark!(params, batches, pallet_utxo, Utxo); add_benchmark!(params, batches, pallet_pp, Pp); From 9f65dfe031310bc36717a13d6b4e8befad0bbb48 Mon Sep 17 00:00:00 2001 From: b-yap <2826165+b-yap@users.noreply.github.com> Date: Tue, 23 Nov 2021 14:43:11 +0800 Subject: [PATCH 30/41] assert_raise: disable the smart contract test case --- test/functional/test_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 635b752..b857542 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -65,8 +65,8 @@ 'feature_staking_unlock_not_validator.py', 'feature_staking_withdraw_no_unlock.py', 'feature_staking_withdraw_not_validator.py', - 'feature_smart_contract_test.py', 'feature_staking_unlock_and_withdraw.py' ## should be ran on 20 secs + # 'feature_smart_contract_test.py', ## fix this when we have the time. # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] From 614236423e3d9e22d3abeeadabe55df67c5733c3 Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 24 Nov 2021 14:24:54 +0700 Subject: [PATCH 31/41] Updated docs/README.md with missing parts from README.md --- docs/README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 1afc7fc..ab30588 100644 --- a/docs/README.md +++ b/docs/README.md @@ -162,11 +162,10 @@ If you want to save the blockchain to host, run: docker run -v ~/ml-blockchain:/tmp/ml-core -t mintlayer-core ``` - ## Create a chain specification -In the preceding example, we used `--chain local` which is a predefined "chain spec" that has Alice and Bob specified as validators along with many other useful defaults. -In this example we will create a two-node network using our own custom chain specification. The process generalizes to more nodes in a straightforward manner. +In the preceding example, we used `--chain=Testnet1Spec.json` which is a predefined chain spec provided by Mintlayer. +In this example we will create a network using our own custom chain specification. Rather than writing our chain spec completely from scratch, we'll just make a few modifications to the one we used before. To start, we need to export the chain spec to a file named @@ -242,7 +241,66 @@ the proper storage keys. Finally share the `customSpecRaw.json` with your all the other validators in the network. -**Note**: Because Rust -> Wasm optimized builds aren't reproducible, each person will get a slightly different Wasm blob which will break consensus if each participant generates the file themselves.For the curious, learn more about this issue in [this blog post](https://dev.to/gnunicorn/hunting-down-a-non-determinism-bug-in-our-rust-wasm-build-4fk1). +**Note**: Because Rust -> Wasm optimized builds aren't reproducible, each person will get a slightly different Wasm blob which will break consensus if each participant generates the file themselves. For the curious, learn more about this issue in [this blog post](https://dev.to/gnunicorn/hunting-down-a-non-determinism-bug-in-our-rust-wasm-build-4fk1). + +### Connect with Polkadot-JS Apps Front-end + +Once the node is running locally, you can connect it to the **Polkadot-JS Apps** front-end +to interact with your chain. [Click here](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) to connect your node. + +## Project Structure + +### Node + +- Networking: Mintlayer uses [libp2p](https://libp2p.io/) as its native networking stack for all inter-node communication. +- Bootnodes: Mintlayer has [bootnodes](https://github.com/mintlayer/core/blob/master/assets/bootnodes.json) that a new node will attempt to connect to, unless a specific node is specified by the user. +- Consensus: Mintlayer uses [AURA](https://docs.rs/sc-consensus-aura/0.9.0/sc_consensus_aura/) as its base consensus algorithm for the time being. There will be an update to introduce [DSA](https://www.mintlayer.org/docs/DSA-consensus-paper-draft.pdf) in the future. +- Finality: Since we are using AURA for consensus, we currently rely on [GRANDPA](https://docs.rs/sc-finality-grandpa/0.9.0/sc_finality_grandpa/) for finality. +- Chain Spec: You can find our chain specification in [chain_spec.rs](https://github.com/mintlayer/core/blob/master/node/src/chain_spec.rs). It defines the basics of the chain such as the genesis block and the bootnodes. +- Services: [service.rs](https://github.com/mintlayer/core/blob/master/node/src/service.rs) defines the node implementation itself. It is here you'll find the consensus setup. + +### Runtime + +For more information on what a [runtime](https://substrate.dev/docs/en/knowledgebase/getting-started/glossary#runtime) is follow the link. +Code in the runtime must be written in `no_std` Rust since it compiles to Wasm. + +- lib.rs: The main file in Mintlayer's runtime. Here you'll find the Mintlayer-specific code for block production such as the block production period. +- staking.rs: Mintlayer's staking implementation. + + +### Pallets + +Mintlayer relies on a host of Substrate pallets as well as a few Mintlayer-specific pallets: +- pp: The implementation of programmable pools on Mintlayer. These are essentially Wasm smart contracts. +- utxo: Mintlayer's UTXO system. + +### Libs + +The `libs` is home to code that Mintlayer relies on but isn't technically a pallet: +- chainscript: Mintlayer's Bitcoin script implementation. +- bech32: code for handling transactions with destinations encoded using bech32 + +### Testing + +Unit tests for particular modules are scattered throughout the codebase. +The `test` directory is home to Mintlayer's functional test framework, which includes system-level tests. Heavily based on Bitcoin's functional test framework. + +### Crypto +Currently, Mintlayer uses Schnorr signatures for crypto-related needs. We plan to move to our BLS implementation in the near future. + +### Contributing +[See this guide](https://github.com/mintlayer/core/blob/master/CONTRIBUTING.md) + +### Branches +The key branches of this repository are `master` and `staging`. + +`staging` is used as the development branch, while `master` is used for fully tested. + +To submit a fix or a feature, code your changes on a new branch based on `staging` and create a pull request. This will trigger CI checks and notify the Minlayer team that a new pull request is awaiting review. Once all CI checks have passed and the pull request has been reviewed and approved by a member of the Mintlayer team, the pull request can be merged into `staging`. + +Periodically, the Mintlayer team will merge `staging` into `master`. +Only a select few members of the Mintlayer team have push access to `master` + ## Firewall rules From 3e9fbf54dbb46180791968694e1e504cfe2f937b Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 24 Nov 2021 14:49:27 +0700 Subject: [PATCH 32/41] README.md to point to docs/README.md --- README.md | 146 +----------------------------------------------------- 1 file changed, 1 insertion(+), 145 deletions(-) mode change 100644 => 120000 README.md diff --git a/README.md b/README.md deleted file mode 100644 index ef4383d..0000000 --- a/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# Mintlayer core - -https://www.mintlayer.org/ - -For a more technical introduction to Mintlayer visit [our docs](https://docs.mintlayer.org/). - -A draft of the consensus paper can be found [here](https://www.mintlayer.org/docs/DSA-consensus-paper-draft.pdf). - -## Security issues -If you find an issue related to the security of Mintlayer then please contact us at security@mintlayer.org so we can address the issue. Mintlayer has a [bug bounty program](https://www.mintlayer.org/bug-bounties) so if your security issue is valid you are elligble for a reward paid in MLT. Do not disclose the security issue publicly until the core Mintlayer team has agreed the issue can be disclosed. See [SECURITY.md](https://github.com/mintlayer/core/blob/master/SECURITY.md) for more info. - -## Bugs -Non-security related bugs should be opened as [issues](https://github.com/mintlayer/core/issues/new) in the core Mintlayer repo. Give as much detail as possible. If you want to fix a bug then see our guidelines for [contributing](https://github.com/mintlayer/core/blob/master/CONTRIBUTING.md). - -## How to build and run Mintlayer - -### Rust Setup - -First, complete the [basic Rust setup instructions](https://github.com/mintlayer/core/blob/master/doc/rust-setup.md). - -### Run - -Use Rust's native `cargo` command to build and launch the template node: - -```sh -cargo run --release -- --dev --tmp -``` - -### Build - -The `cargo run` command will perform an initial build. Use the following command to build the node -without launching it: - -```sh -cargo build --release -``` -or - -`cargo build` to build a debug version - -to purge the local chain run `./target/release/mintlayer-core purge-chain --dev` - -### Docs - -Once the project has been built, the following command can be used to explore all parameters and -subcommands: - -```sh -./target/release/mintlayer-core -h -``` - -You can also find docs in the docs directory and within the directory for a specific pallet or lib. - - -### Single-Node Development Chain - -This command will start the single-node development chain with persistent state: - -```bash -./target/release/mintlayer-core --dev -``` - -Purge the development chain's state: - -```bash -./target/release/mintlayer-core purge-chain --dev -``` - -Start the development chain with detailed logging: - -```bash -RUST_LOG=debug RUST_BACKTRACE=1 ./target/release/mintlayer-core -lruntime=debug --dev -``` - -### Connect with Polkadot-JS Apps Front-end - -Once the node is running locally, you can connect it with **Polkadot-JS Apps** front-end -to interact with your chain. [Click here](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) connecting the Apps to your local node. - -### Connect with Mintlayer's UI -TODO - -### Multi-Node Local Testnet - -If you want to see the multi-node consensus algorithm in action, refer to -[our Start a Private Network tutorial](https://substrate.dev/docs/en/tutorials/start-a-private-network/). - -## Project Structure - -### Node - -- Networking: Mintlayer uses [libp2p](https://libp2p.io/) as its native networking stack for all inter-node communication. -- Bootnodes: Mintlayer has [bootnodes](https://github.com/mintlayer/core/blob/master/assets/bootnodes.json) that a new node will attempt to boot to unless a specific node is specified by the user -- Consensus: Mintlayer uses [AURA](https://docs.rs/sc-consensus-aura/0.9.0/sc_consensus_aura/) as its base consensus algorithm for the time being. There will be an update to introduce [DSA](https://www.mintlayer.org/docs/DSA-consensus-paper-draft.pdf) in the future but DSA is still in development. -- Finality: Since we are using AURA for our consensus we currently rely on [GRANDPA](https://docs.rs/sc-finality-grandpa/0.9.0/sc_finality_grandpa/) for finality. -- Chain Spec: You can find our chain specification in [chain_spec.rs](https://github.com/mintlayer/core/blob/master/node/src/chain_spec.rs). It defines the basics of the chain such as the genesis block and the bootnodes. -- Services: [service.rs](https://github.com/mintlayer/core/blob/master/node/src/service.rs) defines the node implementation itself. It is here you'll find the consensus setup. - - -### Runtime - -For more information on what a [runtime](https://substrate.dev/docs/en/knowledgebase/getting-started/glossary#runtime) is follow the link. -Code in the runtime must be written in `no_std` Rust since it compiles to Wasm. - -- lib.rs: The main file in Mintlayer's runtime. Here you'll find the Mintlayer specific code for block production such as the block production period. -- staking.rs: Here you'll find Mintlayer's staking implementation. - - -### Pallets - -Mintlayer relies on a host of Substrate pallets and a few Mintlayer specific pallets. - -- pp: The implementation of programmable pools on Mintlayer. Essentially Wasm smart contracts -- utxo: Mintlayer's UTXO system - -### Libs - -Libs is home to code that is code that Mintlayer relies on but isn't technically a pallet. - -- chainscript: Mintlayer's bitcoin script implementation. -- bech32: code for handling transactions with destinations encoded using bech32 - -### Testing - -You'll find unit tests littered throughout the codebase but the test directory is home to the functional test framework which is heavily based on Bitcoin's functional test framework. - -### Crypto -As it stands Mintlayer uses Schnorr for all crypto-related things. There is a plan to move to our BLS implementation in the near future but this, as it stands, is a work in progress. - -### Contributing -[See this guide](https://github.com/mintlayer/core/blob/master/CONTRIBUTING.md) - -### Branches -The key branches are master and staging. Master is used for fully tested code, staging is used as the development branch. Fixes or features should be created on new branches branched from staging. A PR is then created to merge the branch into staging where it will require a review from a member of the Mintlayer team. To merge into master create a PR to merge staging to master, a review is required and CI will run. Only select people have push access to master. - -### Firewall rules - -The node uses TCP port 30333 for communications, this needs to be opened if you want to allow -inbound connections. - -Using UFW: -`sudo ufw allow 30333/tcp` - -Using iptables: -`sudo iptables -A INPUT -p tcp --dport 30333 -j ACCEPT` diff --git a/README.md b/README.md new file mode 120000 index 0000000..0e01b43 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +docs/README.md \ No newline at end of file From 44594ac7911ed843a2210a6a190e32c212cc2eea Mon Sep 17 00:00:00 2001 From: Roy Ben Abraham Date: Wed, 24 Nov 2021 15:40:52 +0700 Subject: [PATCH 33/41] Added sections on security and bugs --- docs/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index ab30588..55b5515 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,4 @@ - -# RUNNING +# Running ## Quick start Binaries can be found [here](https://github.com/mintlayer/core/releases). @@ -301,6 +300,11 @@ To submit a fix or a feature, code your changes on a new branch based on `stagin Periodically, the Mintlayer team will merge `staging` into `master`. Only a select few members of the Mintlayer team have push access to `master` +## Security issues +If you find an issue related to the security of Mintlayer then please contact us at security@mintlayer.org so we can address the issue. Mintlayer has a [bug bounty program](https://www.mintlayer.org/bug-bounties) so if your security issue is valid you are elligble for a reward paid in MLT. Do not disclose the security issue publicly until the core Mintlayer team has agreed the issue can be disclosed. See [SECURITY.md](https://github.com/mintlayer/core/blob/master/SECURITY.md) for more info. + +## Bugs +Non-security related bugs should be opened as [issues](https://github.com/mintlayer/core/issues/new) in the core Mintlayer repo. Give as much detail as possible. If you want to fix a bug then see our guidelines for [contributing](https://github.com/mintlayer/core/blob/master/CONTRIBUTING.md). ## Firewall rules From b13d46b41fceb21c37cecbf9023d55498360519b Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Wed, 10 Nov 2021 15:49:41 +0000 Subject: [PATCH 34/41] utxo: Initial support for unsigned transactions --- pallets/utxo/src/lib.rs | 29 +++++++++++++---------------- scripts/wallet.py | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index 065216d..4cc064e 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -1089,7 +1089,6 @@ pub mod pallet { /// Update storage to reflect changes made by transaction /// Where each utxo key is a hash of the entire transaction and its order in the TransactionOutputs vector pub fn update_storage( - caller: &T::AccountId, tx: &TransactionFor, reward: Value, ) -> DispatchResultWithPostInfo { @@ -1144,14 +1143,14 @@ pub mod pallet { } } Destination::CreatePP(script, data) => { - log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); - >::insert(hash, output); - create::(caller, script, hash, output.value, &data)?; + //log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); + //>::insert(hash, output); + //create::(caller, script, hash, output.value, &data)?; } Destination::CallPP(acct_id, fund, data) => { - log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); - >::insert(hash, output); - call::(caller, acct_id, hash, output.value, *fund, data)?; + //log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); + //>::insert(hash, output); + //call::(caller, acct_id, hash, output.value, *fund, data)?; } Destination::LockForStaking { .. } => { staking::lock_for_staking::(hash, output)?; @@ -1166,12 +1165,11 @@ pub mod pallet { } pub fn spend( - caller: &T::AccountId, tx: &TransactionFor, ) -> DispatchResultWithPostInfo { let tx_validity = validate_transaction::(tx)?; ensure!(tx_validity.requires.is_empty(), "missing inputs"); - update_storage::(caller, tx, tx_validity.priority as Value)?; + update_storage::(tx, tx_validity.priority as Value)?; Ok(().into()) } @@ -1215,10 +1213,10 @@ pub mod pallet { impl Pallet { #[pallet::weight(::WeightInfo::spend(tx.inputs.len().saturating_add(tx.outputs.len()) as u32))] pub fn spend( - origin: OriginFor, + _origin: OriginFor, tx: Transaction, ) -> DispatchResultWithPostInfo { - spend::(&ensure_signed(origin)?, &tx)?; + spend::(&tx)?; Self::deposit_event(Event::::TransactionSuccess(tx)); Ok(().into()) } @@ -1287,7 +1285,7 @@ pub mod pallet { .ok_or(DispatchError::Other("Failed to sign the transaction"))?; } - spend::(&signer, &tx) + spend::(&tx) } /// unlock the stake using the STASH ACCOUNT. Stops validating, and allow access to withdraw. @@ -1417,14 +1415,13 @@ where type AccountId = T::AccountId; fn spend( - caller: &T::AccountId, + _caller: &T::AccountId, value: u128, address: H256, utxo: H256, sig: H512, ) -> DispatchResultWithPostInfo { spend::( - caller, &Transaction { inputs: vec![TransactionInput::new_with_signature(utxo, sig)], outputs: vec![TransactionOutputFor::::new_pubkey(value, address)], @@ -1458,7 +1455,7 @@ where time_lock: Default::default(), }; - spend::(caller, &tx).map_err(|_| "Failed to spend the transaction!")?; + spend::(&tx).map_err(|_| "Failed to spend the transaction!")?; Ok(()) } @@ -1475,7 +1472,7 @@ where time_lock: Default::default(), }; - spend::(caller, &tx).map_err(|_| "Failed to spend the transaction!")?; + spend::(&tx).map_err(|_| "Failed to spend the transaction!")?; Ok(()) } } diff --git a/scripts/wallet.py b/scripts/wallet.py index c317256..feff5a9 100755 --- a/scripts/wallet.py +++ b/scripts/wallet.py @@ -53,10 +53,19 @@ def withdrawal_era(self): def current_era(self): return self.client.current_era() + def submit(self, tx): + call = self.client.substrate.compose_call( + call_module = 'Utxo', + call_function = 'spend', + call_params = { 'tx': tx.json() }, + ) + extrinsic = self.client.substrate.create_unsigned_extrinsic(call=call) + return self.client.substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True) + def balance(args): print('Total Free:', Account(args).mlt_balance(), 'MLT') - print('Total Locked:', Account(args).locked_mlt_balance(), 'MLT' ) + print('Total Locked:', Account(args).locked_mlt_balance(), 'MLT') def print_key_info(keypair): print('Seed hex :', keypair.seed_hex or 'UNKNOWN') @@ -128,7 +137,7 @@ def lock(args): ), ] ).sign(acct.keypair, [u for (_, u) in utxos]) - acct.client.submit(acct.keypair, tx) + acct.submit(tx) def lock_extra(args): @@ -136,6 +145,7 @@ def lock_extra(args): utxo_value = Decimal() utxos = [] amount = int(args.amount * MLT_UNIT) + fee = int() if amount < 0: raise Exception('Sending a negative amount') @@ -171,7 +181,7 @@ def lock_extra(args): ), ] ).sign(acct.keypair, [u for (_, u) in utxos]) - acct.client.submit(acct.keypair, tx) + acct.submit(tx) def pay(args): @@ -179,6 +189,11 @@ def pay(args): utxo_value = Decimal() utxos = [] amount = int(args.amount * MLT_UNIT) + fee = int(args.fee * MLT_UNIT) + total = amount + fee + + if fee >= 2 ** 64: + raise Exception('Fee too high') if amount < 0: raise Exception('Sending a negative amount') @@ -186,18 +201,14 @@ def pay(args): for (h, u) in acct.mlt_utxos(): utxos.append((h, u)) utxo_value += u.value - if utxo_value >= amount: + if utxo_value >= total: break - if utxo_value < amount: - raise Exception('Not enough funds') - - fee = int(MLT_UNIT / 10) # TODO - if fee >= 2 ** 64: - raise Exception('Fee too large') - change = utxo_value - amount - fee + if change < 0: + raise Exception('Not enough funds') + tx = mint.Transaction( acct.client, inputs=[ mint.Input(h) for (h, _) in utxos ], @@ -214,7 +225,7 @@ def pay(args): ), ] ).sign(acct.keypair, [u for (_, u) in utxos]) - acct.client.submit(acct.keypair, tx) + acct.submit(tx) def parse_command_line(): ap = argparse.ArgumentParser(description='Mintlayer command line interface') @@ -248,6 +259,8 @@ def parse_command_line(): pay_cmd = sub.add_parser('pay', help='Submit a payment') pay_cmd.set_defaults(func=pay) + pay_cmd.add_argument('--fee', type=Decimal, default=Decimal('0.1'), metavar='AMOUNT', + help='Specify the amount of MLT paid in fees') pay_cmd.add_argument('key', type=str, metavar='SENDER_KEY', help='Sender private key') pay_cmd.add_argument('to', type=str, metavar='RECEPIENT_PUBKEY', @@ -295,6 +308,9 @@ def parse_command_line(): def main(): try: cmd = parse_command_line() + if hasattr(cmd, 'key') and os.path.isfile(cmd.key): + with open(cmd.key) as f: + cmd.key = f.readline().strip() cmd.func(cmd) except Exception as e: print(e) From d3900ebe16f73572c460a5591b7ad0cf31495934 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Tue, 16 Nov 2021 11:46:21 +0000 Subject: [PATCH 35/41] utxo: Supress unused variable warnings --- pallets/utxo/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index 4cc064e..6453797 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -1142,12 +1142,12 @@ pub mod pallet { Some(OutputData::TokenTransferV1 { .. }) | None => continue, } } - Destination::CreatePP(script, data) => { + Destination::CreatePP(_script, _data) => { //log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); //>::insert(hash, output); //create::(caller, script, hash, output.value, &data)?; } - Destination::CallPP(acct_id, fund, data) => { + Destination::CallPP(_acct_id, _fund, _data) => { //log::debug!("inserting to UtxoStore {:?} as key {:?}", output, hash); //>::insert(hash, output); //call::(caller, acct_id, hash, output.value, *fund, data)?; @@ -1441,7 +1441,7 @@ where } fn submit_c2pk_tx( - caller: &T::AccountId, + _caller: &T::AccountId, dest: &T::AccountId, value: u128, outpoints: &Vec, @@ -1460,7 +1460,7 @@ where } fn submit_c2c_tx( - caller: &Self::AccountId, + _caller: &Self::AccountId, dest: &Self::AccountId, value: u128, data: &Vec, From 7c95b150addad21c186f754cd2d31c62e1e12330 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Fri, 19 Nov 2021 11:28:47 +0000 Subject: [PATCH 36/41] utxo: Enforce transactions are unsigned --- pallets/utxo/src/lib.rs | 3 +- pallets/utxo/src/staking_tests.rs | 18 +++--- pallets/utxo/src/tests.rs | 98 +++++++++++++++---------------- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index 6453797..c6e3325 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -1213,9 +1213,10 @@ pub mod pallet { impl Pallet { #[pallet::weight(::WeightInfo::spend(tx.inputs.len().saturating_add(tx.outputs.len()) as u32))] pub fn spend( - _origin: OriginFor, + origin: OriginFor, tx: Transaction, ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; spend::(&tx)?; Self::deposit_event(Event::::TransactionSuccess(tx)); Ok(().into()) diff --git a/pallets/utxo/src/staking_tests.rs b/pallets/utxo/src/staking_tests.rs index 45a59f1..503a2dd 100644 --- a/pallets/utxo/src/staking_tests.rs +++ b/pallets/utxo/src/staking_tests.rs @@ -42,7 +42,7 @@ fn staking_first_time() { .sign(&[utxo], 0, &karl_pub_key) .expect("karl's pub key not found"); let utxo = &tx1.outputs[0]; - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx1.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx1.clone())); let tx2 = Transaction { inputs: vec![TransactionInput::new_empty(tx1.outpoint(0))], @@ -66,7 +66,7 @@ fn staking_first_time() { .expect("Alice's pub key not found"); let new_utxo_hash = tx2.outpoint(1); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx2)); + assert_ok!(Utxo::spend(Origin::none(), tx2)); assert!(UtxoStore::::contains_key(new_utxo_hash)); assert!(StakingCount::::contains_key(H256::from(greg_pub_key))); assert!(StakingCount::::contains_key(H256::from( @@ -109,7 +109,7 @@ fn simple_staking() { let locked_utxo_hash = tx.outpoint(0); let new_utxo_hash = tx.outpoint(1); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); assert!(UtxoStore::::contains_key(new_utxo_hash)); assert!(LockedUtxos::::contains_key(locked_utxo_hash)); assert!(StakingCount::::contains_key(H256::from( @@ -148,7 +148,7 @@ fn less_than_minimum_stake() { tx.inputs[0].witness = karl_sig.0.to_vec(); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "output value must be equal or more than the minimum stake" ); }) @@ -193,7 +193,7 @@ fn non_mlt_staking() { .expect("karl's pub key not found"); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "only MLT tokens are supported for staking" ); }) @@ -224,7 +224,7 @@ fn controller_staking_again() { .expect(" tom's pub key not found"); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "StashAccountAlreadyRegistered" ); }) @@ -256,7 +256,7 @@ fn stash_account_is_staking() { .expect("alice's public key not found"); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "StashAccountAlreadyRegistered" ); }) @@ -288,7 +288,7 @@ fn simple_staking_extra() { let locked_utxo_hash = tx.outpoint(0); let new_utxo_hash = tx.outpoint(1); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); assert!(UtxoStore::::contains_key(new_utxo_hash)); assert!(LockedUtxos::::contains_key(locked_utxo_hash)); assert_eq!( @@ -324,7 +324,7 @@ fn non_validator_staking_extra() { .expect("greg's pub key not found"); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "StashAccountNotFound" ); }) diff --git a/pallets/utxo/src/tests.rs b/pallets/utxo/src/tests.rs index 214e06e..50c64cd 100644 --- a/pallets/utxo/src/tests.rs +++ b/pallets/utxo/src/tests.rs @@ -99,8 +99,8 @@ fn test_script_preimage() { time_lock: Default::default(), }; - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx1)); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx2)); + assert_ok!(Utxo::spend(Origin::none(), tx1)); + assert_ok!(Utxo::spend(Origin::none(), tx2)); }) } @@ -131,7 +131,7 @@ fn test_unchecked_2nd_output() { UtxoStore::::insert(utxo1_hash, &tx1.outputs[1]); // When adding a transaction, the output should be reported as already present. assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx1), + Utxo::spend(Origin::none(), tx1), "output already exists" ); }) @@ -156,7 +156,7 @@ fn test_simple_tx() { let (_, init_utxo) = genesis_utxo(); assert!(UtxoStore::::contains_key(H256::from(init_utxo))); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); assert!(!UtxoStore::::contains_key(H256::from(init_utxo))); assert!(UtxoStore::::contains_key(new_utxo_hash)); assert_eq!( @@ -181,7 +181,7 @@ fn attack_with_sending_to_own_account() { tx.inputs[0].witness = karl_sig.0.to_vec(); assert_noop!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "missing inputs" ); }); @@ -193,13 +193,13 @@ fn attack_with_empty_transactions() { // We should use the real input because. Otherwise, appears another error let (_, input) = tx_input_gen_no_signature(); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), Transaction::default()), // empty tx + Utxo::spend(Origin::none(), Transaction::default()), // empty tx "no inputs" ); assert_err!( Utxo::spend( - Origin::signed(H256::zero()), + Origin::none(), Transaction { inputs: vec![input], // an empty tx outputs: vec![], @@ -226,7 +226,7 @@ fn attack_by_double_counting_input() { .sign_unchecked(&utxos[..], 1, &alice_pub_key); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "each input should be used only once" ); }); @@ -244,7 +244,7 @@ fn attack_with_invalid_signature() { }; assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "signature must be valid" ); }); @@ -263,7 +263,7 @@ fn attack_by_permanently_sinking_outputs() { .sign_unchecked(&[utxo0], 0, &alice_pub_key); assert_noop!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "output value must be nonzero" ); }); @@ -285,7 +285,7 @@ fn attack_by_overflowing_value() { .sign_unchecked(&[utxo0], 0, &alice_pub_key); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "output value overflow" ); }); @@ -307,7 +307,7 @@ fn attack_by_overspending() { .sign_unchecked(&[utxo0], 0, &alice_pub_key); assert_noop!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "output value must not exceed input value" ); }) @@ -334,7 +334,7 @@ fn tx_from_alice_to_karl() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let new_utxo_hash = tx.outpoint(1); let new_utxo = tx.outputs[1].clone(); @@ -349,7 +349,7 @@ fn tx_from_alice_to_karl() { } .sign_unchecked(&[new_utxo], 0, &alice_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); }); } @@ -376,7 +376,7 @@ fn test_reward() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let utxo_hash = tx.outpoint(0); // if the previous spend succeeded, there should be one utxo @@ -413,7 +413,7 @@ fn test_reward_overflow() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "reward exceed allowed amount" ); }) @@ -433,7 +433,7 @@ fn test_script() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); }) } @@ -451,7 +451,7 @@ fn test_time_lock_tx() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "Time lock restrictions not satisfied", ); }) @@ -473,7 +473,7 @@ fn test_time_lock_script_fail() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); let outpoint = tx1.outpoint(0); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx1)); + assert_ok!(Utxo::spend(Origin::none(), tx1)); // The following should fail because the transaction-level time lock does not conform to // the time lock restrictions imposed by the scripting system. @@ -486,7 +486,7 @@ fn test_time_lock_script_fail() { time_lock: Default::default(), }; assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx2), + Utxo::spend(Origin::none(), tx2), "script verification failed" ); }) @@ -508,7 +508,7 @@ fn attack_double_spend_by_tweaking_input() { time_lock: Default::default(), } .sign_unchecked(&[utxo0], 0, &alice_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx0.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx0.clone())); // Create a transaction that spends the same input 10 times by slightly modifying the // redeem script. @@ -527,7 +527,7 @@ fn attack_double_spend_by_tweaking_input() { time_lock: Default::default(), }; assert_err!( - Utxo::spend(Origin::signed(H256::zero()), tx1), + Utxo::spend(Origin::none(), tx1), "each input should be used only once" ); }); @@ -670,14 +670,14 @@ proptest! { } .sign_unchecked(&[utxo0], 0, &alice); let outpoint = tx1.outpoint(0); - assert!(Utxo::spend(Origin::signed(H256::zero()), tx1).is_ok()); + assert!(Utxo::spend(Origin::none(), tx1).is_ok()); let tx2 = Transaction { inputs: vec![TransactionInput::new_script(outpoint, script, Default::default())], outputs: vec![TransactionOutput::new_pubkey(ALICE_GENESIS_BALANCE - u32::MAX as Value, H256::from(alice))], time_lock: tx_lock_time, }; - Utxo::spend(Origin::signed(H256::zero()), tx2) + Utxo::spend(Origin::none(), tx2) }); // The transaction should be accepted if and only if: @@ -818,7 +818,7 @@ fn test_token_issuance() { // After calling `Utxo::spend`, we should check that Storages successfully changed. // If it successfully wrote a new UTXO in the Storage, tx goes through all verifications correctly. assert!(UtxoStore::::contains_key(H256::from(init_utxo))); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); assert!(!UtxoStore::::contains_key(H256::from(init_utxo))); // Checking a new UTXO assert!(UtxoStore::::contains_key(new_utxo_hash)); @@ -868,7 +868,7 @@ fn test_token_issuance() { // let new_utxo_hash = tx.outpoint(0); // let (_, init_utxo) = genesis_utxo(); // assert!(UtxoStore::::contains_key(H256::from(init_utxo))); -// assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); +// assert_ok!(Utxo::spend(Origin::none(), tx)); // assert!(!UtxoStore::::contains_key(H256::from(init_utxo))); // assert!(UtxoStore::::contains_key(new_utxo_hash)); // assert_eq!( @@ -914,7 +914,7 @@ fn test_token_issuance() { // let (_, init_utxo) = genesis_utxo(); // // Submit // assert!(UtxoStore::::contains_key(H256::from(init_utxo))); -// assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); +// assert_ok!(Utxo::spend(Origin::none(), tx.clone())); // assert!(!UtxoStore::::contains_key(H256::from(init_utxo))); // // Checking a new UTXO // assert!(UtxoStore::::contains_key(new_utxo_hash)); @@ -939,7 +939,7 @@ fn test_token_issuance() { // // Submit // assert!(UtxoStore::::contains_key(H256::from(new_utxo_hash))); // frame_support::assert_err_ignore_postinfo!( -// Utxo::spend(Origin::signed(H256::zero()), tx), +// Utxo::spend(Origin::none(), tx), // "digital data has already been minted" // ); // }); @@ -968,14 +968,14 @@ macro_rules! test_tx { // We can check what error we are expecting if stringify!($checking) == "Err" { frame_support::assert_err_ignore_postinfo!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), $err ); assert!(UtxoStore::::contains_key(H256::from(init_utxo))); assert!(!UtxoStore::::contains_key(new_utxo_hash)); } else if stringify!($checking) == "Ok" { // We can check is that success - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx)); + assert_ok!(Utxo::spend(Origin::none(), tx)); assert!(!UtxoStore::::contains_key(H256::from(init_utxo))); assert!(UtxoStore::::contains_key(new_utxo_hash)); } @@ -1115,7 +1115,7 @@ fn test_two_token_creation_in_one_tx() { } .sign_unchecked(&[utxo0], 0, &alice_pub_key); frame_support::assert_err_ignore_postinfo!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "this id can't be used for a new token" ); }); @@ -1153,7 +1153,7 @@ where } .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); let token_id = TokenId::new(&tx.inputs[0]); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let token_utxo_hash = tx.outpoint(1); let token_utxo: TransactionOutput = tx.outputs[1].clone(); @@ -1166,7 +1166,7 @@ where token_utxo, ); frame_support::assert_err_ignore_postinfo!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), expecting_err_msg ); }); @@ -1309,7 +1309,7 @@ fn test_token_transfer() { } .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); let token_id = TokenId::new(&tx.inputs[0]); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let token_utxo_hash = tx.outpoint(1); let token_utxo = tx.outputs[1].clone(); @@ -1337,7 +1337,7 @@ fn test_token_transfer() { time_lock: Default::default(), } .sign_unchecked(&[token_utxo.clone()], 0, &karl_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let alice_tokens_utxo_hash = tx.outpoint(0); let karl_tokens_utxo_hash = tx.outpoint(1); let karl_tokens_utxo = tx.outputs[1].clone(); @@ -1371,7 +1371,7 @@ fn test_token_transfer() { time_lock: Default::default(), } .sign_unchecked(&[karl_tokens_utxo], 0, &karl_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); assert_eq!( 300_000_000, UtxoStore::::get(alice_tokens_utxo_hash) @@ -1426,7 +1426,7 @@ fn test_token_transfer() { // time_lock: Default::default(), // } // .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); -// assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); +// assert_ok!(Utxo::spend(Origin::none(), tx.clone())); // let token_utxo_hash = tx.outpoint(1); // let token_utxo = tx.outputs[1].clone(); // @@ -1445,7 +1445,7 @@ fn test_token_transfer() { // } // .sign_unchecked(&[token_utxo.clone()], 0, &karl_pub_key); // frame_support::assert_err_ignore_postinfo!( -// Utxo::spend(Origin::signed(H256::zero()), tx), +// Utxo::spend(Origin::none(), tx), // "input for the token not found" // ); // // Let's fail on exceed token amount @@ -1463,7 +1463,7 @@ fn test_token_transfer() { // } // .sign_unchecked(&[token_utxo.clone()], 0, &karl_pub_key); // frame_support::assert_err_ignore_postinfo!( -// Utxo::spend(Origin::signed(H256::zero()), tx), +// Utxo::spend(Origin::none(), tx), // "output value must not exceed input value" // ); // @@ -1482,7 +1482,7 @@ fn test_token_transfer() { // } // .sign_unchecked(&[token_utxo.clone()], 0, &karl_pub_key); // frame_support::assert_err_ignore_postinfo!( -// Utxo::spend(Origin::signed(H256::zero()), tx), +// Utxo::spend(Origin::none(), tx), // "output value must not exceed input value" // ); // @@ -1500,7 +1500,7 @@ fn test_token_transfer() { // time_lock: Default::default(), // } // .sign_unchecked(&[token_utxo], 0, &karl_pub_key); -// assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); +// assert_ok!(Utxo::spend(Origin::none(), tx.clone())); // let nft_utxo_hash = tx.outpoint(0); // assert!(!UtxoStore::::contains_key(H256::from( // token_utxo_hash @@ -1549,7 +1549,7 @@ fn test_token_creation_with_insufficient_fee() { } .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let token_utxo_hash = tx.outpoint(1); let token_utxo = tx.outputs[1].clone(); let tx = Transaction { @@ -1571,7 +1571,7 @@ fn test_token_creation_with_insufficient_fee() { } .sign_unchecked(&[token_utxo], 0, &karl_pub_key); frame_support::assert_err_ignore_postinfo!( - Utxo::spend(Origin::signed(H256::zero()), tx), + Utxo::spend(Origin::none(), tx), "insufficient fee" ); }); @@ -1605,7 +1605,7 @@ fn test_transfer_and_issuance_in_one_tx() { } .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); let first_issuance_token_id = TokenId::new(&tx.inputs[0]); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let token_utxo_hash = tx.outpoint(1); let token_utxo = tx.outputs[1].clone(); @@ -1644,7 +1644,7 @@ fn test_transfer_and_issuance_in_one_tx() { time_lock: Default::default(), } .sign_unchecked(&[token_utxo.clone()], 0, &karl_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let alice_transfer_utxo_hash = tx.outpoint(0); let karl_transfer_utxo_hash = tx.outpoint(1); let karl_issuance_utxo_hash = tx.outpoint(2); @@ -1732,7 +1732,7 @@ fn test_transfer_for_multiple_tokens() { } .sign_unchecked(&[utxo0.clone()], 0, &alice_pub_key); let tkn1_token_id = TokenId::new(&tx.inputs[0]); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let tkn1_utxo_hash = tx.outpoint(0); let tkn1_utxo = tx.outputs[0].clone(); // @@ -1765,7 +1765,7 @@ fn test_transfer_for_multiple_tokens() { } .sign_unchecked(&[tkn1_utxo.clone()], 0, &karl_pub_key); let tkn2_token_id = TokenId::new(&tx.inputs[0]); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let tkn1_utxo_hash = tx.outpoint(0); let tkn2_utxo_hash = tx.outpoint(1); // @@ -1809,7 +1809,7 @@ fn test_transfer_for_multiple_tokens() { .sign_unchecked(&prev_utxos, 0, &alice_pub_key) .sign_unchecked(&prev_utxos, 1, &alice_pub_key); let tkn3_token_id = TokenId::new(&tx.inputs[0]); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let tkn1_utxo_hash = tx.outpoint(0); let tkn2_utxo_hash = tx.outpoint(1); let tkn3_utxo_hash = tx.outpoint(2); @@ -1854,7 +1854,7 @@ fn test_transfer_for_multiple_tokens() { .sign_unchecked(&prev_utxos, 0, &karl_pub_key) .sign_unchecked(&prev_utxos, 1, &karl_pub_key) .sign_unchecked(&prev_utxos, 2, &karl_pub_key); - assert_ok!(Utxo::spend(Origin::signed(H256::zero()), tx.clone())); + assert_ok!(Utxo::spend(Origin::none(), tx.clone())); let tkn1_utxo_hash = tx.outpoint(0); let tkn2_utxo_hash = tx.outpoint(1); let tkn3_utxo_hash = tx.outpoint(2); From ae59e99e5d78148c377f23f3d37fd407d7d5e9ca Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Fri, 19 Nov 2021 11:28:17 +0000 Subject: [PATCH 37/41] wallet: Custom RPC result handling --- scripts/wallet.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/scripts/wallet.py b/scripts/wallet.py index feff5a9..2bb0f47 100755 --- a/scripts/wallet.py +++ b/scripts/wallet.py @@ -60,7 +60,25 @@ def submit(self, tx): call_params = { 'tx': tx.json() }, ) extrinsic = self.client.substrate.create_unsigned_extrinsic(call=call) - return self.client.substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True) + + def result_handler(message, update_nr, sub_id): + if 'params' in message and type(message['params']['result']) is dict: + res = message['params']['result'] + if 'inBlock' in res: + return { + 'block_hash': res['inBlock'] + } + + result = self.client.substrate.rpc_request( + "author_submitAndWatchExtrinsic", + [str(extrinsic.data)], + result_handler=result_handler + ) + + if 'block_hash' in result: + print('Transaction included in block', result['block_hash']) + + return result def balance(args): From 7fb3d898ee0700d692fb0fdb091fb1d25ad8286f Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Wed, 24 Nov 2021 16:12:15 +0000 Subject: [PATCH 38/41] tests: Fix unsigned tx submission in most tests --- .../test_framework/mintlayer/utxo.py | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/test/functional/test_framework/mintlayer/utxo.py b/test/functional/test_framework/mintlayer/utxo.py index a4c1a61..296c6ad 100644 --- a/test/functional/test_framework/mintlayer/utxo.py +++ b/test/functional/test_framework/mintlayer/utxo.py @@ -95,6 +95,25 @@ def get_ledger(self, keypair): def withdrawal_era(self, keypair): return self.staking.withdrawal_era(keypair) + def submit_extrinsic(self, extrinsic): + def result_handler(message, update_nr, sub_id): + if 'params' in message and type(message['params']['result']) is dict: + res = message['params']['result'] + if 'inBlock' in res: + return substrateinterface.ExtrinsicReceipt( + substrate = self.substrate, + block_hash = res['inBlock'], + extrinsic_hash = "Unknown" + ) + + result = self.substrate.rpc_request( + "author_submitAndWatchExtrinsic", + [str(extrinsic.data)], + result_handler=result_handler + ) + + return result + """ Submit a transaction onto the blockchain """ def submit(self, keypair, tx): call = self.substrate.compose_call( @@ -102,13 +121,13 @@ def submit(self, keypair, tx): call_function = 'spend', call_params = { 'tx': tx.json() }, ) - extrinsic = self.substrate.create_signed_extrinsic(call=call, keypair=keypair) + extrinsic = self.substrate.create_unsigned_extrinsic(call=call) self.log.debug("extrinsic submitted...") try: - receipt = self.substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True) + receipt = self.submit_extrinsic(extrinsic) self.log.debug("Extrinsic '{}' sent and included in block '{}'".format(receipt.extrinsic_hash, receipt.block_hash)) - return (receipt.extrinsic_hash, receipt.block_hash, receipt.triggered_events) + return (receipt.extrinsic_hash, receipt.block_hash, []) except SubstrateRequestException as e: self.log.debug("Failed to send: {}".format(e)) raise e From b56d8e4efeaadcc7dcd896d147256dfc108fee38 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Thu, 25 Nov 2021 08:59:24 +0000 Subject: [PATCH 39/41] tests: Fix up the staking_extra test to work with unsigned txns --- test/functional/feature_staking_extra.py | 12 +++++++----- test/functional/test_framework/mintlayer/utxo.py | 8 +++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/functional/feature_staking_extra.py b/test/functional/feature_staking_extra.py index ad45f0d..5850366 100755 --- a/test/functional/feature_staking_extra.py +++ b/test/functional/feature_staking_extra.py @@ -91,13 +91,15 @@ def run_test(self): ) ] ).sign(alice_stash, [utxos[0][1]]) - (_,_,events) = client.submit(alice_stash, tx1) + (_, block_hash, _events) = client.submit(alice_stash, tx1) - assert_equal(events[0].value['module_id'],'Staking') - assert_equal(events[0].value['event_id'], 'Bonded') + events = client.substrate.get_events(block_hash = block_hash) - assert_equal(events[1].value['module_id'],'Utxo') - assert_equal(events[1].value['event_id'], 'TransactionSuccess') + assert_equal(events[1].value['module_id'],'Staking') + assert_equal(events[1].value['event_id'], 'Bonded') + + assert_equal(events[2].value['module_id'],'Utxo') + assert_equal(events[2].value['event_id'], 'TransactionSuccess') # Get Alice stash new_count = list(client.get_staking_count(alice_stash))[0][1] diff --git a/test/functional/test_framework/mintlayer/utxo.py b/test/functional/test_framework/mintlayer/utxo.py index 296c6ad..cea7e00 100644 --- a/test/functional/test_framework/mintlayer/utxo.py +++ b/test/functional/test_framework/mintlayer/utxo.py @@ -96,14 +96,16 @@ def withdrawal_era(self, keypair): return self.staking.withdrawal_era(keypair) def submit_extrinsic(self, extrinsic): + extrinsic.extrinsic_hash = extrinsic.generate_hash() def result_handler(message, update_nr, sub_id): + print('Msg:', message) if 'params' in message and type(message['params']['result']) is dict: res = message['params']['result'] if 'inBlock' in res: return substrateinterface.ExtrinsicReceipt( substrate = self.substrate, block_hash = res['inBlock'], - extrinsic_hash = "Unknown" + extrinsic_hash = '0x{}'.format(extrinsic.extrinsic_hash) ) result = self.substrate.rpc_request( @@ -122,12 +124,12 @@ def submit(self, keypair, tx): call_params = { 'tx': tx.json() }, ) extrinsic = self.substrate.create_unsigned_extrinsic(call=call) - self.log.debug("extrinsic submitted...") try: + self.log.debug("Submitting extrinsic") receipt = self.submit_extrinsic(extrinsic) self.log.debug("Extrinsic '{}' sent and included in block '{}'".format(receipt.extrinsic_hash, receipt.block_hash)) - return (receipt.extrinsic_hash, receipt.block_hash, []) + return (receipt.extrinsic_hash, receipt.block_hash, None) except SubstrateRequestException as e: self.log.debug("Failed to send: {}".format(e)) raise e From cdde90ef2ec78b4a02861a86a9c9d644959eb077 Mon Sep 17 00:00:00 2001 From: Lukas Kuklinek Date: Sun, 28 Nov 2021 00:15:45 +0000 Subject: [PATCH 40/41] tests: Add a test for unsigned transaction submission --- test/functional/feature_alice_rain_test.py | 111 +++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 112 insertions(+) create mode 100755 test/functional/feature_alice_rain_test.py diff --git a/test/functional/feature_alice_rain_test.py b/test/functional/feature_alice_rain_test.py new file mode 100755 index 0000000..9aaf4fe --- /dev/null +++ b/test/functional/feature_alice_rain_test.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 RBB S.r.l +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""An example functional test + +Send a transaction from Alice to Bob, then spend Bob's transaction +""" + +from substrateinterface import Keypair +import test_framework.mintlayer.utxo as utxo +from test_framework.messages import COIN + +from test_framework.test_framework import MintlayerTestFramework +from test_framework.util import ( + assert_equal, + connect_nodes, + wait_until, +) + + +class ExampleTest(MintlayerTestFramework): + # Each functional test is a subclass of the MintlayerTestFramework class. + + # Override the set_test_params(), add_options(), setup_chain(), setup_network() + # and setup_nodes() methods to customize the test setup as required. + + def set_test_params(self): + """Override test parameters for your individual test. + + This method must be overridden and num_nodes must be exlicitly set.""" + self.setup_clean_chain = True + self.num_nodes = 3 + # Use self.extra_args to change command-line arguments for the nodes + self.extra_args = [["--validator=false"], [], []] + + # self.log.info("I've finished set_test_params") # Oops! Can't run self.log before run_test() + + def setup_network(self): + """Setup the test network topology + + Often you won't need to override this, since the standard network topology + (linear: node0 <-> node1 <-> node2 <-> ...) is fine for most tests. + + If you do override this method, remember to start the nodes, assign + them to self.nodes, connect them and then sync.""" + + self.setup_nodes() + + def custom_method(self): + """Do some custom behaviour for this test + + Define it in a method here because you're going to use it repeatedly. + If you think it's useful in general, consider moving it to the base + MintlayerTestFramework class so other tests can use it.""" + + self.log.info("Running custom_method") + + def run_test(self): + client = self.nodes[0].rpc_client + + alice = Keypair.create_from_uri('//Alice') + rain = Keypair.create_from_uri('//Rain') + rude = Keypair.create_from_uri('//Rude') + + # fetch the genesis utxo from storage + utxos = list(client.utxos_for(alice)) + + tx1 = utxo.Transaction( + client, + inputs=[ + utxo.Input(utxos[0][0]), + ], + outputs=[ + utxo.Output( + value=50 * COIN, + destination=utxo.DestPubkey(rain.public_key), + data=None + ), + utxo.Output( + value=39999999949 * COIN, + destination=utxo.DestPubkey(alice.public_key), + data=None + ) + ] + ).sign(alice, [utxos[0][1]]) + client.submit(alice, tx1) + + tx2 = utxo.Transaction( + client, + inputs=[ + utxo.Input(tx1.outpoint(0)), + ], + outputs=[ + utxo.Output( + value=30 * COIN, + destination=utxo.DestPubkey(rude.public_key), + data=None + ), + utxo.Output( + value=19 * COIN, + destination=utxo.DestPubkey(rain.public_key), + data=None + ), + ] + ).sign(rain, [tx1.outputs[0]]) + client.submit(rain, tx2) + +if __name__ == '__main__': + ExampleTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index b857542..0d61947 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -56,6 +56,7 @@ BASE_SCRIPTS= [ 'example_test.py', 'feature_alice_bob_test.py', + 'feature_alice_rain_test.py', 'feature_staking_extra.py', 'feature_staking_extra_not_validator.py', 'feature_staking_extra_wrong_controller.py', From cecaee3aa82c6f7d5bf4bedfb6abe7e345ab9967 Mon Sep 17 00:00:00 2001 From: Aaro Altonen Date: Mon, 29 Nov 2021 10:35:04 +0200 Subject: [PATCH 41/41] utxo: Disable programmable pools --- pallets/utxo/src/lib.rs | 50 ++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index c6e3325..f011638 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -565,36 +565,36 @@ pub mod pallet { } pub fn create( - caller: &T::AccountId, - code: &Vec, - utxo_hash: H256, - utxo_value: u128, - data: &Vec, + _caller: &T::AccountId, + _code: &Vec, + _utxo_hash: H256, + _utxo_value: u128, + _data: &Vec, ) -> Result<(), &'static str> { - let weight: Weight = 6000000000; - - T::ProgrammablePool::create(caller, weight, code, utxo_hash, utxo_value, data) + // let weight: Weight = 6000000000; + // T::ProgrammablePool::create(caller, weight, code, utxo_hash, utxo_value, data) + Ok(()) } pub fn call( - caller: &T::AccountId, - dest: &T::AccountId, - utxo_hash: H256, - utxo_value: u128, - fund_contract: bool, - data: &Vec, + _caller: &T::AccountId, + _dest: &T::AccountId, + _utxo_hash: H256, + _utxo_value: u128, + _fund_contract: bool, + _data: &Vec, ) -> Result<(), &'static str> { - let weight: Weight = 6000000000; - - T::ProgrammablePool::call( - caller, - dest, - weight, - utxo_hash, - utxo_value, - fund_contract, - data, - ) + // let weight: Weight = 6000000000; + // T::ProgrammablePool::call( + // caller, + // dest, + // weight, + // utxo_hash, + // utxo_value, + // fund_contract, + // data, + // ) + Ok(()) } pub fn validate_transaction(