Skip to content

Commit

Permalink
[replay-fuzz] Add additional mutations to the transaction-replay fuzz…
Browse files Browse the repository at this point in the history
…er (MystenLabs#12079)

## Description 

Adds a bunch of new mutations to the transaction replay fuzzer, and also
adds a way to random select a different mutators from a set of them.

## Test Plan 

Ran them locally
  • Loading branch information
tzakian authored May 19, 2023
1 parent e54462c commit 012dfba
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 37 deletions.
31 changes: 0 additions & 31 deletions crates/sui-replay/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::{
transaction_provider::{TransactionProvider, TransactionSource},
types::ReplayEngineError,
};
use rand::seq::SliceRandom;

// Step 1: Get a transaction T from the network
// Step 2: Create the sandbox and verify the TX does not fork locally
Expand Down Expand Up @@ -203,33 +202,3 @@ impl From<ReplayEngineError> for ReplayFuzzError {
ReplayFuzzError::LocalExecError { err }
}
}

pub struct ShuffleMutator {
pub rng: rand::rngs::StdRng,
pub num_mutations_per_base_left: u64,
}

impl TransactionKindMutator for ShuffleMutator {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
if self.num_mutations_per_base_left == 0 {
// Nothing else to do
return None;
}

self.num_mutations_per_base_left -= 1;
if let TransactionKind::ProgrammableTransaction(mut p) = transaction_kind.clone() {
// Simple command and arg shuffle mutation
// TODO: do more complicated mutations
p.commands.shuffle(&mut self.rng);
p.inputs.shuffle(&mut self.rng);
Some(TransactionKind::ProgrammableTransaction(p))
} else {
// Other types not supported yet
None
}
}

fn reset(&mut self, mutations_per_base: u64) {
self.num_mutations_per_base_left = mutations_per_base;
}
}
146 changes: 146 additions & 0 deletions crates/sui-replay/src/fuzz_mutations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use rand::{seq::SliceRandom, SeedableRng};
use sui_types::transaction::TransactionKind;

use crate::fuzz::TransactionKindMutator;

pub mod drop_random_command_suffix;
pub mod drop_random_commands;
pub mod shuffle_command_inputs;
pub mod shuffle_commands;
pub mod shuffle_transaction_inputs;
pub mod shuffle_types;

// The number of times that we will try to select a different mutator if the selected one is unable
// to be applied for some reason.
const NUM_TRIES: u64 = 5;

// Combiners for `TransactionKindMutator`s:
// * `RandomMutator` will select a random mutator from a list of mutators
// * `ChainedMutator` will apply a list of mutators in sequence. If a given mutator doesn't apply
// it will be skipped but other mutations both before and after the failed mutator may still be applied.
pub struct RandomMutator {
pub rng: rand::rngs::StdRng,
pub mutators: Vec<Box<dyn TransactionKindMutator + Send + Sync>>,
pub num_tries: u64,
}

pub struct ChainedMutator {
pub mutators: Vec<Box<dyn TransactionKindMutator>>,
}

impl RandomMutator {
pub fn new() -> Self {
Self {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
mutators: vec![],
num_tries: NUM_TRIES,
}
}

pub fn add_mutator(&mut self, mutator: Box<dyn TransactionKindMutator + Send + Sync>) {
self.mutators.push(mutator);
}

pub fn select_mutator(&mut self) -> Option<&mut Box<dyn TransactionKindMutator + Send + Sync>> {
self.mutators.choose_mut(&mut self.rng)
}
}

impl Default for RandomMutator {
fn default() -> Self {
Self::new()
}
}

impl TransactionKindMutator for RandomMutator {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
for _ in 0..self.num_tries {
if let Some(mutator) = self.select_mutator() {
return mutator.mutate(transaction_kind);
}
}
None
}

fn reset(&mut self, mutations_per_base: u64) {
for mutator in self.mutators.iter_mut() {
mutator.reset(mutations_per_base);
}
}
}

impl ChainedMutator {
pub fn new() -> Self {
Self { mutators: vec![] }
}

pub fn add_mutator(&mut self, mutator: Box<dyn TransactionKindMutator>) {
self.mutators.push(mutator);
}
}

impl Default for ChainedMutator {
fn default() -> Self {
Self::new()
}
}

impl TransactionKindMutator for ChainedMutator {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
let mut mutated = transaction_kind.clone();
let mut num_mutations = 0;

for mutator in self.mutators.iter_mut() {
if let Some(new_mutated) = mutator.mutate(&mutated) {
num_mutations += 1;
mutated = new_mutated;
}
}

if num_mutations == 0 {
None
} else {
Some(mutated)
}
}

fn reset(&mut self, mutations_per_base: u64) {
for mutator in self.mutators.iter_mut() {
mutator.reset(mutations_per_base);
}
}
}

pub fn base_fuzzers(num_mutations: u64) -> RandomMutator {
let mut mutator = RandomMutator::new();
mutator.add_mutator(Box::new(shuffle_commands::ShuffleCommands {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
num_mutations_per_base_left: num_mutations,
}));
mutator.add_mutator(Box::new(shuffle_types::ShuffleTypes {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
num_mutations_per_base_left: num_mutations,
}));
mutator.add_mutator(Box::new(shuffle_command_inputs::ShuffleCommandInputs {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
num_mutations_per_base_left: num_mutations,
}));
mutator.add_mutator(Box::new(
shuffle_transaction_inputs::ShuffleTransactionInputs {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
num_mutations_per_base_left: num_mutations,
},
));
mutator.add_mutator(Box::new(drop_random_commands::DropRandomCommands {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
num_mutations_per_base_left: num_mutations,
}));
mutator.add_mutator(Box::new(drop_random_command_suffix::DropCommandSuffix {
rng: rand::rngs::StdRng::from_seed([0u8; 32]),
num_mutations_per_base_left: num_mutations,
}));
mutator
}
39 changes: 39 additions & 0 deletions crates/sui-replay/src/fuzz_mutations/drop_random_command_suffix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::fuzz::TransactionKindMutator;
use rand::Rng;
use sui_types::transaction::TransactionKind;
use tracing::info;

pub struct DropCommandSuffix {
pub rng: rand::rngs::StdRng,
pub num_mutations_per_base_left: u64,
}

impl TransactionKindMutator for DropCommandSuffix {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
if self.num_mutations_per_base_left == 0 {
// Nothing else to do
return None;
}

self.num_mutations_per_base_left -= 1;
if let TransactionKind::ProgrammableTransaction(mut p) = transaction_kind.clone() {
if p.commands.is_empty() {
return None;
}
let slice_index = self.rng.gen_range(0..p.commands.len());
p.commands.truncate(slice_index);
info!("Mutation: Dropping command suffix");
Some(TransactionKind::ProgrammableTransaction(p))
} else {
// Other types not supported yet
None
}
}

fn reset(&mut self, mutations_per_base: u64) {
self.num_mutations_per_base_left = mutations_per_base;
}
}
42 changes: 42 additions & 0 deletions crates/sui-replay/src/fuzz_mutations/drop_random_commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::fuzz::TransactionKindMutator;
use rand::seq::SliceRandom;
use sui_types::transaction::TransactionKind;
use tracing::info;

pub struct DropRandomCommands {
pub rng: rand::rngs::StdRng,
pub num_mutations_per_base_left: u64,
}

impl TransactionKindMutator for DropRandomCommands {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
if self.num_mutations_per_base_left == 0 {
// Nothing else to do
return None;
}

self.num_mutations_per_base_left -= 1;
if let TransactionKind::ProgrammableTransaction(mut p) = transaction_kind.clone() {
if p.commands.is_empty() {
return None;
}
p.commands = p
.commands
.choose_multiple(&mut self.rng, p.commands.len() - 1)
.cloned()
.collect();
info!("Mutation: Dropping random commands");
Some(TransactionKind::ProgrammableTransaction(p))
} else {
// Other types not supported yet
None
}
}

fn reset(&mut self, mutations_per_base: u64) {
self.num_mutations_per_base_left = mutations_per_base;
}
}
53 changes: 53 additions & 0 deletions crates/sui-replay/src/fuzz_mutations/shuffle_command_inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::fuzz::TransactionKindMutator;
use rand::seq::SliceRandom;
use sui_types::transaction::{Command, TransactionKind};
use tracing::info;

pub struct ShuffleCommandInputs {
pub rng: rand::rngs::StdRng,
pub num_mutations_per_base_left: u64,
}

impl ShuffleCommandInputs {
fn shuffle_command(&mut self, command: &mut Command) {
match command {
Command::MakeMoveVec(_, ref mut args)
| Command::MergeCoins(_, ref mut args)
| Command::SplitCoins(_, ref mut args)
| Command::TransferObjects(ref mut args, _) => {
args.shuffle(&mut self.rng);
}
Command::MoveCall(ref mut pt) => pt.arguments.shuffle(&mut self.rng),
Command::Publish(_, _) => (),
Command::Upgrade(_, _, _, _) => (),
}
}
}

impl TransactionKindMutator for ShuffleCommandInputs {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
if self.num_mutations_per_base_left == 0 {
// Nothing else to do
return None;
}

self.num_mutations_per_base_left -= 1;
if let TransactionKind::ProgrammableTransaction(mut p) = transaction_kind.clone() {
for command in &mut p.commands {
self.shuffle_command(command);
}
info!("Mutation: Shuffling command inputs");
Some(TransactionKind::ProgrammableTransaction(p))
} else {
// Other types not supported yet
None
}
}

fn reset(&mut self, mutations_per_base: u64) {
self.num_mutations_per_base_left = mutations_per_base;
}
}
35 changes: 35 additions & 0 deletions crates/sui-replay/src/fuzz_mutations/shuffle_commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::fuzz::TransactionKindMutator;
use rand::seq::SliceRandom;
use sui_types::transaction::TransactionKind;
use tracing::info;

pub struct ShuffleCommands {
pub rng: rand::rngs::StdRng,
pub num_mutations_per_base_left: u64,
}

impl TransactionKindMutator for ShuffleCommands {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
if self.num_mutations_per_base_left == 0 {
// Nothing else to do
return None;
}

self.num_mutations_per_base_left -= 1;
if let TransactionKind::ProgrammableTransaction(mut p) = transaction_kind.clone() {
p.commands.shuffle(&mut self.rng);
info!("Mutation: Shuffling commands");
Some(TransactionKind::ProgrammableTransaction(p))
} else {
// Other types not supported yet
None
}
}

fn reset(&mut self, mutations_per_base: u64) {
self.num_mutations_per_base_left = mutations_per_base;
}
}
35 changes: 35 additions & 0 deletions crates/sui-replay/src/fuzz_mutations/shuffle_transaction_inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::fuzz::TransactionKindMutator;
use rand::seq::SliceRandom;
use sui_types::transaction::TransactionKind;
use tracing::info;

pub struct ShuffleTransactionInputs {
pub rng: rand::rngs::StdRng,
pub num_mutations_per_base_left: u64,
}

impl TransactionKindMutator for ShuffleTransactionInputs {
fn mutate(&mut self, transaction_kind: &TransactionKind) -> Option<TransactionKind> {
if self.num_mutations_per_base_left == 0 {
// Nothing else to do
return None;
}

self.num_mutations_per_base_left -= 1;
if let TransactionKind::ProgrammableTransaction(mut p) = transaction_kind.clone() {
p.inputs.shuffle(&mut self.rng);
info!("Mutation: Shuffling transaction inputs");
Some(TransactionKind::ProgrammableTransaction(p))
} else {
// Other types not supported yet
None
}
}

fn reset(&mut self, mutations_per_base: u64) {
self.num_mutations_per_base_left = mutations_per_base;
}
}
Loading

0 comments on commit 012dfba

Please sign in to comment.