4 Commits

Author SHA1 Message Date
Luke Parker
d3464cfcb3 Extend Monero action with support for macOS 2025-12-09 23:07:29 -05:00
Luke Parker
8dbea8452d Intentionally attempt to produce finalizations every single block
While aggressive, the existing value (given the documentation for it) is far
too large to be reasonable here.
2025-12-09 21:59:20 -05:00
Luke Parker
f94b7ca50e Add verification of SignedBatch to the in-instructions pallet 2025-12-09 21:58:18 -05:00
Luke Parker
5e39f9bc1e Add gash as a shell tested with
Its notable as shell used within Guix.
2025-12-09 20:26:23 -05:00
10 changed files with 170 additions and 20 deletions

View File

@@ -19,25 +19,32 @@ runs:
- name: Download the Monero Daemon - name: Download the Monero Daemon
if: steps.cache-monerod.outputs.cache-hit != 'true' if: steps.cache-monerod.outputs.cache-hit != 'true'
# Calculates OS/ARCH to demonstrate it, yet then locks to linux-x64 due
# to the contained folder not following the same naming scheme and
# requiring further expansion not worth doing right now
shell: bash shell: bash
run: | run: |
RUNNER_OS=${{ runner.os }} OS=${{ runner.os }}
RUNNER_ARCH=${{ runner.arch }} ARCH=${{ runner.arch }}
RUNNER_OS=${RUNNER_OS,,} OS=$(echo "$OS" | tr "[:upper:]" "[:lower:]")
RUNNER_ARCH=${RUNNER_ARCH,,} ARCH=$(echo "$ARCH" | tr "[:upper:]" "[:lower:]")
RUNNER_OS=linux if [ "$OS" = "windows" ]; then
RUNNER_ARCH=x64 OS=win
echo "Windows is unsupported at this time"
exit 1
fi
if [ "$OS" = "macos" ]; then
OS=mac
fi
if [ "$ARCH" = "arm64" ]; then
ARCH=armv8
fi
FILE=monero-$RUNNER_OS-$RUNNER_ARCH-${{ inputs.version }}.tar.bz2 FILE=monero-$OS-$ARCH-${{ inputs.version }}.tar.bz2
wget https://downloads.getmonero.org/cli/$FILE wget https://downloads.getmonero.org/cli/$FILE
tar -xvf $FILE tar -xvf $FILE
rm $FILE
sudo mv monero-x86_64-linux-gnu-${{ inputs.version }}/monerod /usr/bin/monerod sudo mv $(find . -name monerod) /usr/bin/monerod
sudo chmod 777 /usr/bin/monerod sudo chmod 777 /usr/bin/monerod
sudo chmod +x /usr/bin/monerod sudo chmod +x /usr/bin/monerod

View File

@@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
- name: Install cargo deny - name: Install cargo deny
run: cargo +1.91.1 install cargo-deny --version =0.18.8 run: cargo +1.91.1 install cargo-deny --version =0.18.9
- name: Run cargo deny - name: Run cargo deny
run: cargo deny -L error --all-features check --hide-inclusion-graph run: cargo deny -L error --all-features check --hide-inclusion-graph

View File

@@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0 - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
- name: Install cargo deny - name: Install cargo deny
run: cargo +1.91.1 install cargo-deny --version =0.18.8 run: cargo +1.91.1 install cargo-deny --version =0.18.9
- name: Run cargo deny - name: Run cargo deny
run: cargo deny -L error --all-features check --hide-inclusion-graph run: cargo deny -L error --all-features check --hide-inclusion-graph

View File

@@ -45,11 +45,11 @@ jobs:
./muslstack -s "$STACK" ./monerod-muslstack ./muslstack -s "$STACK" ./monerod-muslstack
sudo apt update -y sudo apt update -y
sudo apt install -y ksh bash dash zsh busybox mksh posh yash sudo apt install -y ksh bash dash zsh busybox mksh posh gash yash
sudo ln -s "$(which busybox)" /usr/bin/ash sudo ln -s "$(which busybox)" /usr/bin/ash
sudo ln -s "$(which busybox)" /usr/bin/hush sudo ln -s "$(which busybox)" /usr/bin/hush
cargo install brush-shell cargo install brush-shell
for shell in sh ksh bash dash zsh ash hush mksh lksh posh yash brush; do for shell in sh ksh bash dash zsh ash hush mksh lksh posh gash yash brush; do
cp monerod monerod-idss-$shell cp monerod monerod-idss-$shell
ln -s "$(which $shell)" sh ln -s "$(which $shell)" sh
./sh ./orchestration/increase_default_stack_size.sh monerod-idss-$shell ./sh ./orchestration/increase_default_stack_size.sh monerod-idss-$shell

10
Cargo.lock generated
View File

@@ -92,9 +92,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "alloy-chains" name = "alloy-chains"
version = "0.2.21" version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ebac8ff9c2f07667e1803dc777304337e160ce5153335beb45e8ec0751808" checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"num_enum", "num_enum",
@@ -4321,9 +4321,9 @@ dependencies = [
[[package]] [[package]]
name = "libp2p-identity" name = "libp2p-identity"
version = "0.2.12" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774" checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3"
dependencies = [ dependencies = [
"bs58", "bs58",
"ed25519-dalek", "ed25519-dalek",
@@ -8441,7 +8441,9 @@ dependencies = [
"serai-core-pallet", "serai-core-pallet",
"serai-dex-pallet", "serai-dex-pallet",
"serai-genesis-liquidity-pallet", "serai-genesis-liquidity-pallet",
"serai-signals-pallet",
"serai-validator-sets-pallet", "serai-validator-sets-pallet",
"sp-application-crypto",
"sp-core", "sp-core",
] ]

View File

@@ -23,6 +23,7 @@ workspace = true
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false } frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
@@ -32,6 +33,7 @@ serai-abi = { path = "../abi", default-features = false, features = ["substrate"
serai-core-pallet = { path = "../core", default-features = false } serai-core-pallet = { path = "../core", default-features = false }
serai-coins-pallet = { path = "../coins", default-features = false } serai-coins-pallet = { path = "../coins", default-features = false }
serai-validator-sets-pallet = { path = "../validator-sets", default-features = false } serai-validator-sets-pallet = { path = "../validator-sets", default-features = false }
serai-signals-pallet = { path = "../signals", default-features = false }
serai-dex-pallet = { path = "../dex", default-features = false } serai-dex-pallet = { path = "../dex", default-features = false }
serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false } serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false }
@@ -40,6 +42,7 @@ std = [
"scale/std", "scale/std",
"sp-core/std", "sp-core/std",
"sp-application-crypto/std",
"frame-system/std", "frame-system/std",
"frame-support/std", "frame-support/std",
@@ -49,6 +52,7 @@ std = [
"serai-core-pallet/std", "serai-core-pallet/std",
"serai-coins-pallet/std", "serai-coins-pallet/std",
"serai-validator-sets-pallet/std", "serai-validator-sets-pallet/std",
"serai-signals-pallet/std",
"serai-dex-pallet/std", "serai-dex-pallet/std",
"serai-genesis-liquidity-pallet/std", "serai-genesis-liquidity-pallet/std",
] ]
@@ -61,6 +65,7 @@ try-runtime = [
"serai-core-pallet/try-runtime", "serai-core-pallet/try-runtime",
"serai-coins-pallet/try-runtime", "serai-coins-pallet/try-runtime",
"serai-validator-sets-pallet/try-runtime", "serai-validator-sets-pallet/try-runtime",
"serai-signals-pallet/try-runtime",
"serai-dex-pallet/try-runtime", "serai-dex-pallet/try-runtime",
"serai-genesis-liquidity-pallet/try-runtime", "serai-genesis-liquidity-pallet/try-runtime",
] ]
@@ -72,6 +77,7 @@ runtime-benchmarks = [
"serai-core-pallet/runtime-benchmarks", "serai-core-pallet/runtime-benchmarks",
"serai-coins-pallet/runtime-benchmarks", "serai-coins-pallet/runtime-benchmarks",
"serai-validator-sets-pallet/runtime-benchmarks", "serai-validator-sets-pallet/runtime-benchmarks",
"serai-signals-pallet/runtime-benchmarks",
"serai-dex-pallet/runtime-benchmarks", "serai-dex-pallet/runtime-benchmarks",
"serai-genesis-liquidity-pallet/runtime-benchmarks", "serai-genesis-liquidity-pallet/runtime-benchmarks",
] ]

View File

@@ -7,6 +7,8 @@ extern crate alloc;
#[expect(clippy::cast_possible_truncation)] #[expect(clippy::cast_possible_truncation)]
#[frame_support::pallet] #[frame_support::pallet]
mod pallet { mod pallet {
use sp_application_crypto::RuntimePublic;
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
@@ -26,6 +28,7 @@ mod pallet {
+ serai_core_pallet::Config + serai_core_pallet::Config
+ serai_coins_pallet::Config<serai_coins_pallet::CoinsInstance> + serai_coins_pallet::Config<serai_coins_pallet::CoinsInstance>
+ serai_validator_sets_pallet::Config + serai_validator_sets_pallet::Config
+ serai_signals_pallet::Config
+ serai_coins_pallet::Config<serai_coins_pallet::LiquidityTokensInstance> + serai_coins_pallet::Config<serai_coins_pallet::LiquidityTokensInstance>
+ serai_dex_pallet::Config + serai_dex_pallet::Config
+ serai_genesis_liquidity_pallet::Config + serai_genesis_liquidity_pallet::Config
@@ -36,6 +39,18 @@ mod pallet {
#[pallet::error] #[pallet::error]
pub enum Error<T> {} pub enum Error<T> {}
/// The block the last batch was published during.
///
/// This is used to restrict publication of batches to once-per-block, limiting the impact of a
/// compromised publisher from bloating the Serai blockchain with spam.
#[pallet::storage]
type BlockOfLastBatch<T: Config> =
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
/// The ID of the last batch which was published.
#[pallet::storage]
type LastBatch<T: Config> = StorageMap<_, Identity, ExternalNetworkId, u32, OptionQuery>;
/// The Pallet struct. /// The Pallet struct.
#[pallet::pallet] #[pallet::pallet]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
@@ -55,6 +70,113 @@ mod pallet {
todo!("TODO") todo!("TODO")
} }
} }
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
let batch = match call {
Call::execute_batch { batch } => batch,
Call::__Ignore(_, _) => Err(InvalidTransaction::Call)?,
};
let network = batch.batch.network();
// Verify the network isn't halted
if serai_signals_pallet::Pallet::<T>::halted(network) {
Err(InvalidTransaction::Custom(1))?;
}
// Verify the `Batch`'s signature
let mut signed_by_latest_decided_session = false;
{
let message = batch.batch.publish_batch_message();
let signed_by_session = |session| {
let Some(key) =
serai_validator_sets_pallet::Pallet::<T>::oraclization_key(ExternalValidatorSet {
network,
session,
})
else {
return false;
};
key.verify(&message, &batch.signature.into())
};
let Some(current_session) =
serai_validator_sets_pallet::Pallet::<T>::current_session(NetworkId::from(network))
else {
Err(InvalidTransaction::BadProof)?
};
if !signed_by_session(current_session) {
let latest_decided_session =
serai_validator_sets_pallet::Pallet::<T>::latest_decided_session(NetworkId::from(
network,
))
.expect("current session yet never one decided?");
if !signed_by_session(latest_decided_session) {
Err(InvalidTransaction::BadProof)?;
}
signed_by_latest_decided_session = true;
}
}
// Verify this is the first `Batch` for this block
let current_block_number = frame_system::Pallet::<T>::block_number();
if BlockOfLastBatch::<T>::get(network) == Some(current_block_number) {
// This transaction is valid in the future, the next block, but not now
Err(InvalidTransaction::Future)?;
}
// Verify this `Batch` descends immediately from the prior `Batch`
let required_last_batch = batch.batch.id().checked_sub(1);
{
let last_batch = LastBatch::<T>::get(network);
if last_batch < required_last_batch {
Err(InvalidTransaction::Future)?;
}
if last_batch > required_last_batch {
Err(InvalidTransaction::Stale)?;
}
}
/*
Set the metadata fields as necessary for further batches to be verified. While this is
mutating the state within the verification function, it's necessary for the verification of
the following transactions.
Additionally, we know these state changes occur as tests verify we can publish `Batch`es
and have the `LastBatch` field be incremented.
*/
BlockOfLastBatch::<T>::set(network, Some(current_block_number));
LastBatch::<T>::set(network, Some(batch.batch.id()));
if signed_by_latest_decided_session {
// Because the latest decided session, which is not the current session, has taken over
// for publishing `Batch`es, it has agreed to become the current session
serai_validator_sets_pallet::Pallet::<T>::accept_handover(network);
}
let mut builder = ValidTransaction::with_tag_prefix("InInstructions");
if let Some(required_last_batch) = required_last_batch {
// TODO: Should this replace the DB mutations present within this verification function?
builder = builder.and_requires((network, required_last_batch));
}
builder
.and_provides((network, batch.batch.id()))
// Set a 10 block longevity, though this should be included in the next block
.longevity(10)
.propagate(true)
.build()
}
/// Explicitly provide a `pre_dispatch` which calls `validate_unsigned`.
///
/// This is reasonably assumed, and the current provided implementation, but not guaranteed by
/// the documentation.
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ())
}
}
} }
pub use pallet::*; pub use pallet::*;

View File

@@ -433,7 +433,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
grandpa::run_grandpa_voter(grandpa::GrandpaParams { grandpa::run_grandpa_voter(grandpa::GrandpaParams {
config: grandpa::Config { config: grandpa::Config {
gossip_duration: std::time::Duration::from_millis(333), gossip_duration: std::time::Duration::from_millis(333),
justification_generation_period: 512, justification_generation_period: 1,
name: Some(name), name: Some(name),
observer_enabled: false, observer_enabled: false,
keystore: if role.is_authority() { Some(keystore) } else { None }, keystore: if role.is_authority() { Some(keystore) } else { None },

View File

@@ -234,6 +234,11 @@ pub mod pallet {
Ok(()) Ok(())
} }
/// Check if an external network was halted.
pub fn halted(network: ExternalNetworkId) -> bool {
Halted::<T>::contains_key(network)
}
} }
/// An error from the `signals` pallet. /// An error from the `signals` pallet.

View File

@@ -345,6 +345,14 @@ mod pallet {
Ok(()) Ok(())
} }
/// Have the latest decided session become the current session.
///
/// This is restricted to `ExternalNetworkId` as this process happens internally for
/// `NetworkId::Serai`.
pub fn accept_handover(network: ExternalNetworkId) {
Abstractions::<T>::accept_handover(network.into());
}
/* TODO /* TODO
pub fn distribute_block_rewards( pub fn distribute_block_rewards(
network: NetworkId, network: NetworkId,