-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Transaction validity is critical to the chain consistency. The specification of the routines is defined in https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/tx_validity.md Some of these specs may intersect with VM logic; these intersections are outside the scope of this lib and should be implemented directly in the VM. Resolves #3
- Loading branch information
Showing
10 changed files
with
953 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/// Maximum contract size, in bytes. | ||
pub const CONTRACT_MAX_SIZE: u64 = 16 * 1024; | ||
|
||
/// Maximum number of inputs. | ||
pub const MAX_INPUTS: u8 = 8; | ||
|
||
/// Maximum number of outputs. | ||
pub const MAX_OUTPUTS: u8 = 8; | ||
|
||
/// Maximum number of witnesses. | ||
pub const MAX_WITNESSES: u8 = 16; | ||
|
||
/// Maximum gas per transaction. | ||
pub const MAX_GAS_PER_TX: u64 = 1000000; | ||
|
||
// TODO set max script length const | ||
/// Maximum length of script, in instructions. | ||
pub const MAX_SCRIPT_LENGTH: u64 = 1024 * 1024 * 1024; | ||
|
||
// TODO set max script length const | ||
/// Maximum length of script data, in bytes. | ||
pub const MAX_SCRIPT_DATA_LENGTH: u64 = 1024 * 1024 * 1024; | ||
|
||
/// Maximum number of static contracts. | ||
pub const MAX_STATIC_CONTRACTS: u64 = 255; | ||
|
||
// TODO set max predicate length value | ||
/// Maximum length of predicate, in instructions. | ||
pub const MAX_PREDICATE_LENGTH: u64 = 1024 * 1024; | ||
|
||
// TODO set max predicate data length value | ||
/// Maximum length of predicate data, in bytes. | ||
pub const MAX_PREDICATE_DATA_LENGTH: u64 = 1024 * 1024; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
#![feature(arbitrary_enum_discriminant)] | ||
#![feature(is_sorted)] | ||
|
||
// TODO Add docs | ||
|
||
mod transaction; | ||
|
||
pub mod bytes; | ||
pub mod consts; | ||
|
||
pub use transaction::{Color, Id, Input, Output, Root, Transaction, Witness}; | ||
pub use transaction::{Color, Id, Input, Output, Root, Transaction, ValidationError, Witness}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
use super::{Color, Input, Output, Transaction, Witness}; | ||
use crate::consts::*; | ||
|
||
use fuel_asm::Word; | ||
|
||
use std::mem; | ||
|
||
mod error; | ||
|
||
pub use error::ValidationError; | ||
|
||
const COLOR_SIZE: usize = mem::size_of::<Color>(); | ||
|
||
impl Input { | ||
pub fn validate(&self, index: usize, outputs: &[Output], witnesses: &[Witness]) -> Result<(), ValidationError> { | ||
match self { | ||
Self::Coin { predicate, .. } if predicate.len() > MAX_PREDICATE_LENGTH as usize => { | ||
Err(ValidationError::InputCoinPredicateLength { index }) | ||
} | ||
|
||
Self::Coin { predicate_data, .. } if predicate_data.len() > MAX_PREDICATE_DATA_LENGTH as usize => { | ||
Err(ValidationError::InputCoinPredicateDataLength { index }) | ||
} | ||
|
||
Self::Coin { witness_index, .. } if *witness_index as usize >= witnesses.len() => { | ||
Err(ValidationError::InputCoinWitnessIndexBounds { index }) | ||
} | ||
|
||
// ∀ inputContract ∃! outputContract : outputContract.inputIndex = inputContract.index | ||
Self::Contract { .. } | ||
if 1 != outputs | ||
.iter() | ||
.filter_map(|output| match output { | ||
Output::Contract { input_index, .. } if *input_index as usize == index => Some(()), | ||
_ => None, | ||
}) | ||
.count() => | ||
{ | ||
Err(ValidationError::InputContractAssociatedOutputContract { index }) | ||
} | ||
|
||
// TODO If h is the block height the UTXO being spent was created, transaction is | ||
// invalid if `blockheight() < h + maturity`. | ||
_ => Ok(()), | ||
} | ||
} | ||
} | ||
|
||
impl Output { | ||
pub fn validate(&self, index: usize, inputs: &[Input]) -> Result<(), ValidationError> { | ||
match self { | ||
Self::Contract { input_index, .. } => match inputs.get(*input_index as usize) { | ||
Some(Input::Contract { .. }) => Ok(()), | ||
_ => Err(ValidationError::OutputContractInputIndex { index }), | ||
}, | ||
|
||
_ => Ok(()), | ||
} | ||
} | ||
} | ||
|
||
impl Transaction { | ||
pub fn validate(&self, block_height: Word) -> Result<(), ValidationError> { | ||
if self.gas_price() > MAX_GAS_PER_TX { | ||
Err(ValidationError::TransactionGasLimit)? | ||
} | ||
|
||
if block_height < self.maturity() as Word { | ||
Err(ValidationError::TransactionMaturity)?; | ||
} | ||
|
||
if self.inputs().len() > MAX_INPUTS as usize { | ||
Err(ValidationError::TransactionInputsMax)? | ||
} | ||
|
||
if self.outputs().len() > MAX_OUTPUTS as usize { | ||
Err(ValidationError::TransactionOutputsMax)? | ||
} | ||
|
||
if self.witnesses().len() > MAX_WITNESSES as usize { | ||
Err(ValidationError::TransactionWitnessesMax)? | ||
} | ||
|
||
let input_colors: Vec<&Color> = self.input_colors().collect(); | ||
for input_color in input_colors.as_slice() { | ||
if self | ||
.outputs() | ||
.iter() | ||
.filter_map(|output| match output { | ||
Output::Change { color, .. } if color != &Color::default() && input_color == &color => Some(()), | ||
_ => None, | ||
}) | ||
.count() | ||
> 1 | ||
{ | ||
Err(ValidationError::TransactionOutputChangeColorDuplicated)? | ||
} | ||
} | ||
|
||
for (index, input) in self.inputs().iter().enumerate() { | ||
input.validate(index, self.outputs(), self.witnesses())?; | ||
} | ||
|
||
for (index, output) in self.outputs().iter().enumerate() { | ||
output.validate(index, self.inputs())?; | ||
if let Output::Change { color, .. } = output { | ||
if !input_colors.iter().any(|input_color| input_color == &color) { | ||
Err(ValidationError::TransactionOutputChangeColorNotFound)? | ||
} | ||
} | ||
} | ||
|
||
match self { | ||
Self::Script { | ||
outputs, | ||
script, | ||
script_data, | ||
.. | ||
} => { | ||
if script.len() > MAX_SCRIPT_LENGTH as usize { | ||
Err(ValidationError::TransactionScriptLength)?; | ||
} | ||
|
||
if script_data.len() > MAX_SCRIPT_DATA_LENGTH as usize { | ||
Err(ValidationError::TransactionScriptDataLength)?; | ||
} | ||
|
||
outputs | ||
.iter() | ||
.enumerate() | ||
.try_for_each(|(index, output)| match output { | ||
Output::ContractCreated { .. } => { | ||
Err(ValidationError::TransactionScriptOutputContractCreated { index }) | ||
} | ||
_ => Ok(()), | ||
})?; | ||
|
||
Ok(()) | ||
} | ||
|
||
Self::Create { | ||
inputs, | ||
outputs, | ||
witnesses, | ||
bytecode_witness_index, | ||
static_contracts, | ||
.. | ||
} => { | ||
match witnesses.get(*bytecode_witness_index as usize) { | ||
Some(witness) if witness.as_ref().len() as u64 * 4 > CONTRACT_MAX_SIZE => { | ||
Err(ValidationError::TransactionCreateBytecodeLen)? | ||
} | ||
None => Err(ValidationError::TransactionCreateBytecodeWitnessIndex)?, | ||
_ => (), | ||
} | ||
|
||
if static_contracts.len() > MAX_STATIC_CONTRACTS as usize { | ||
Err(ValidationError::TransactionCreateStaticContractsMax)?; | ||
} | ||
|
||
if !static_contracts.as_slice().is_sorted() { | ||
Err(ValidationError::TransactionCreateStaticContractsOrder)?; | ||
} | ||
|
||
// TODO Any contract with ID in staticContracts is not in the state | ||
// TODO The computed contract ID (see below) is not equal to the contractID of | ||
// the one OutputType.ContractCreated output | ||
|
||
for (index, input) in inputs.iter().enumerate() { | ||
if let Input::Contract { .. } = input { | ||
Err(ValidationError::TransactionCreateInputContract { index })? | ||
} | ||
} | ||
|
||
let mut change_color_zero = false; | ||
let mut contract_created = false; | ||
for (index, output) in outputs.iter().enumerate() { | ||
match output { | ||
Output::Contract { .. } => Err(ValidationError::TransactionCreateOutputContract { index })?, | ||
Output::Variable { .. } => Err(ValidationError::TransactionCreateOutputVariable { index })?, | ||
|
||
Output::Change { color, .. } if color == &[0u8; COLOR_SIZE] && change_color_zero => { | ||
Err(ValidationError::TransactionCreateOutputChangeColorZero { index })? | ||
} | ||
Output::Change { color, .. } if color == &[0u8; COLOR_SIZE] => change_color_zero = true, | ||
Output::Change { .. } => { | ||
Err(ValidationError::TransactionCreateOutputChangeColorNonZero { index })? | ||
} | ||
|
||
Output::ContractCreated { .. } if contract_created => { | ||
Err(ValidationError::TransactionCreateOutputContractCreatedMultiple { index })? | ||
} | ||
Output::ContractCreated { .. } => contract_created = true, | ||
|
||
_ => (), | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use std::{error, fmt, io}; | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
pub enum ValidationError { | ||
InputCoinPredicateLength { index: usize }, | ||
InputCoinPredicateDataLength { index: usize }, | ||
InputCoinWitnessIndexBounds { index: usize }, | ||
InputContractAssociatedOutputContract { index: usize }, | ||
OutputContractInputIndex { index: usize }, | ||
TransactionCreateInputContract { index: usize }, | ||
TransactionCreateOutputContract { index: usize }, | ||
TransactionCreateOutputVariable { index: usize }, | ||
TransactionCreateOutputChangeColorZero { index: usize }, | ||
TransactionCreateOutputChangeColorNonZero { index: usize }, | ||
TransactionCreateOutputContractCreatedMultiple { index: usize }, | ||
TransactionCreateBytecodeLen, | ||
TransactionCreateBytecodeWitnessIndex, | ||
TransactionCreateStaticContractsMax, | ||
TransactionCreateStaticContractsOrder, | ||
TransactionScriptLength, | ||
TransactionScriptDataLength, | ||
TransactionScriptOutputContractCreated { index: usize }, | ||
TransactionGasLimit, | ||
TransactionMaturity, | ||
TransactionInputsMax, | ||
TransactionOutputsMax, | ||
TransactionWitnessesMax, | ||
TransactionOutputChangeColorDuplicated, | ||
TransactionOutputChangeColorNotFound, | ||
} | ||
|
||
impl fmt::Display for ValidationError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
// TODO better describe the error variants | ||
write!(f, "{:?}", self) | ||
} | ||
} | ||
|
||
impl error::Error for ValidationError { | ||
fn source(&self) -> Option<&(dyn error::Error + 'static)> { | ||
None | ||
} | ||
} | ||
|
||
impl From<ValidationError> for io::Error { | ||
fn from(v: ValidationError) -> io::Error { | ||
io::Error::new(io::ErrorKind::Other, v) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.