Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only ascii alphanumeric strings in tokens after v1 #1283

Merged
merged 2 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chainstate/src/detail/ban_score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ impl BanScore for TokensError {
impl BanScore for CheckBlockTransactionsError {
fn ban_score(&self) -> u32 {
match self {
CheckBlockTransactionsError::PropertyQueryError(_) => 0,
CheckBlockTransactionsError::DuplicateInputInTransaction(_, _) => 100,
CheckBlockTransactionsError::DuplicateInputInBlock(_) => 100,
CheckBlockTransactionsError::EmptyInputsOutputsInTransactionInBlock(_, _) => 100,
Expand Down
41 changes: 28 additions & 13 deletions chainstate/src/detail/chainstateref/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,15 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
}

fn check_tokens_txs(&self, block: &Block) -> Result<(), CheckBlockTransactionsError> {
let prev_block_id = block.prev_block_id();
let current_height = self
.get_gen_block_index(&prev_block_id)?
.ok_or(CheckBlockTransactionsError::PropertyQueryError(
PropertyQueryError::PrevBlockIndexNotFound(prev_block_id),
))?
.block_height()
.next_height();

for tx in block.transactions() {
// We can't issue multiple tokens in a single tx
let issuance_count = get_tokens_issuance_count(tx.outputs());
Expand All @@ -769,24 +778,30 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
token_data,
tx.transaction(),
block.get_id(),
current_height,
)?;
}
Ok(())
}
TxOutput::IssueFungibleToken(issuance) => {
check_tokens_issuance(self.chain_config, issuance).map_err(|e| {
TokensError::IssueError(e, tx.transaction().get_id(), block.get_id())
})
}
TxOutput::IssueFungibleToken(issuance) => check_tokens_issuance(
self.chain_config,
current_height,
issuance,
)
.map_err(|e| {
TokensError::IssueError(e, tx.transaction().get_id(), block.get_id())
}),
TxOutput::IssueNft(_, issuance, _) => match issuance.as_ref() {
NftIssuance::V0(data) => check_nft_issuance_data(self.chain_config, data)
.map_err(|e| {
TokensError::IssueError(
e,
tx.transaction().get_id(),
block.get_id(),
)
}),
NftIssuance::V0(data) => {
check_nft_issuance_data(self.chain_config, current_height, data)
.map_err(|e| {
TokensError::IssueError(
e,
tx.transaction().get_id(),
block.get_id(),
)
})
}
},
TxOutput::CreateStakePool(_, _)
| TxOutput::ProduceBlockFromStake(_, _)
Expand Down
2 changes: 2 additions & 0 deletions chainstate/src/detail/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ pub enum CheckBlockError {

#[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum CheckBlockTransactionsError {
#[error("Blockchain storage error: {0}")]
PropertyQueryError(#[from] PropertyQueryError),
#[error("Duplicate input in transaction {0} in block {1}")]
DuplicateInputInTransaction(Id<Transaction>, Id<Block>),
#[error("Duplicate input in block")]
Expand Down
100 changes: 81 additions & 19 deletions chainstate/src/detail/tokens/check_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use common::chain::ChainConfig;
use common::{
chain::{tokens::TokenIssuanceVersion, ChainConfig},
primitives::BlockHeight,
};
use tx_verifier::error::TokenIssuanceError;
use utils::ensure;

fn check_is_text_alphanumeric(str: &[u8]) -> bool {
match String::from_utf8(str.to_vec()) {
Ok(text) => text.chars().all(|c| c.is_ascii_alphanumeric()),
match std::str::from_utf8(str) {
Ok(text) => text.chars().all(char::is_alphanumeric),
Err(_) => false,
}
}

fn check_is_text_ascii_alphanumeric(str: &[u8]) -> bool {
str.iter().all(|c| c.is_ascii_alphanumeric())
}

pub fn is_rfc3986_valid_symbol(ch: char) -> bool {
// RFC 3986 alphabet taken from https://www.rfc-editor.org/rfc/rfc3986#section-2.1
"%:/?#[]@!$&\'()*+,;=-._~".chars().any(|rfc1738_ch| ch == rfc1738_ch)
Expand Down Expand Up @@ -53,6 +60,7 @@ pub fn check_media_hash(chain_config: &ChainConfig, hash: &[u8]) -> Result<(), T

pub fn check_token_ticker(
chain_config: &ChainConfig,
current_height: BlockHeight,
ticker: &[u8],
) -> Result<(), TokenIssuanceError> {
// Check length
Expand All @@ -61,31 +69,69 @@ pub fn check_token_ticker(
TokenIssuanceError::IssueErrorInvalidTickerLength
);

// Check is ticker has alphanumeric chars
ensure!(
check_is_text_alphanumeric(ticker),
TokenIssuanceError::IssueErrorTickerHasNoneAlphaNumericChar
);
// Check if ticker has alphanumeric chars
let tokens_version = chain_config
.chainstate_upgrades()
.version_at_height(current_height)
.1
.token_issuance_version();

match tokens_version {
TokenIssuanceVersion::V0 => {
ensure!(
check_is_text_alphanumeric(ticker),
TokenIssuanceError::IssueErrorTickerHasNoneAlphaNumericChar
);
}
TokenIssuanceVersion::V1 => {
ensure!(
check_is_text_ascii_alphanumeric(ticker),
TokenIssuanceError::IssueErrorTickerHasNoneAlphaNumericChar
);
}
}

Ok(())
}

pub fn check_nft_name(chain_config: &ChainConfig, name: &[u8]) -> Result<(), TokenIssuanceError> {
pub fn check_nft_name(
chain_config: &ChainConfig,
current_height: BlockHeight,
name: &[u8],
) -> Result<(), TokenIssuanceError> {
// Check length
ensure!(
name.len() <= chain_config.token_max_name_len() && !name.is_empty(),
TokenIssuanceError::IssueErrorInvalidNameLength
);

// Check is name has alphanumeric chars
ensure!(
check_is_text_alphanumeric(name),
TokenIssuanceError::IssueErrorNameHasNoneAlphaNumericChar
);
// Check if name has alphanumeric chars
let tokens_version = chain_config
.chainstate_upgrades()
.version_at_height(current_height)
.1
.token_issuance_version();

match tokens_version {
TokenIssuanceVersion::V0 => {
ensure!(
check_is_text_alphanumeric(name),
TokenIssuanceError::IssueErrorNameHasNoneAlphaNumericChar
);
}
TokenIssuanceVersion::V1 => {
ensure!(
check_is_text_ascii_alphanumeric(name),
TokenIssuanceError::IssueErrorNameHasNoneAlphaNumericChar
);
}
}
Ok(())
}

pub fn check_nft_description(
chain_config: &ChainConfig,
current_height: BlockHeight,
description: &[u8],
) -> Result<(), TokenIssuanceError> {
// Check length
Expand All @@ -94,10 +140,26 @@ pub fn check_nft_description(
TokenIssuanceError::IssueErrorInvalidDescriptionLength
);

// Check is description has alphanumeric chars
ensure!(
check_is_text_alphanumeric(description),
TokenIssuanceError::IssueErrorDescriptionHasNoneAlphaNumericChar
);
// Check if description has alphanumeric chars
let tokens_version = chain_config
.chainstate_upgrades()
.version_at_height(current_height)
.1
.token_issuance_version();

match tokens_version {
TokenIssuanceVersion::V0 => {
ensure!(
check_is_text_alphanumeric(description),
TokenIssuanceError::IssueErrorDescriptionHasNoneAlphaNumericChar
);
}
TokenIssuanceVersion::V1 => {
ensure!(
check_is_text_ascii_alphanumeric(description),
TokenIssuanceError::IssueErrorDescriptionHasNoneAlphaNumericChar
);
}
}
Ok(())
}
28 changes: 18 additions & 10 deletions chainstate/src/detail/tokens/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use common::{
tokens::{NftIssuanceV0, TokenData, TokenIssuance, TokenIssuanceV0},
Block, ChainConfig, Transaction,
},
primitives::{Amount, Id, Idable},
primitives::{Amount, BlockHeight, Id, Idable},
};
use serialization::{DecodeAll, Encode};
use tx_verifier::error::TokenIssuanceError;
Expand All @@ -33,11 +33,12 @@ use check_utils::{check_nft_description, check_nft_name, check_token_ticker, is_

pub fn check_nft_issuance_data(
chain_config: &ChainConfig,
current_height: BlockHeight,
issuance: &NftIssuanceV0,
) -> Result<(), TokenIssuanceError> {
check_token_ticker(chain_config, &issuance.metadata.ticker)?;
check_nft_name(chain_config, &issuance.metadata.name)?;
check_nft_description(chain_config, &issuance.metadata.description)?;
check_token_ticker(chain_config, current_height, &issuance.metadata.ticker)?;
check_nft_name(chain_config, current_height, &issuance.metadata.name)?;
check_nft_description(chain_config, current_height, &issuance.metadata.description)?;

let icon_uri = Vec::<u8>::decode_all(&mut issuance.metadata.icon_uri.encode().as_slice())
.map_err(|_| TokenIssuanceError::IssueErrorIncorrectIconURI)?;
Expand Down Expand Up @@ -84,10 +85,11 @@ pub fn check_nft_issuance_data(

pub fn check_tokens_issuance_data_v0(
chain_config: &ChainConfig,
current_height: BlockHeight,
issuance_data: &TokenIssuanceV0,
) -> Result<(), TokenIssuanceError> {
// Check token ticker
check_token_ticker(chain_config, &issuance_data.token_ticker)?;
check_token_ticker(chain_config, current_height, &issuance_data.token_ticker)?;

// Check amount
ensure!(
Expand Down Expand Up @@ -116,12 +118,13 @@ pub fn check_tokens_issuance_data_v0(

pub fn check_tokens_issuance(
chain_config: &ChainConfig,
current_height: BlockHeight,
issuance: &TokenIssuance,
) -> Result<(), TokenIssuanceError> {
match issuance {
TokenIssuance::V1(issuance_data) => {
// Check token ticker
check_token_ticker(chain_config, &issuance_data.token_ticker)?;
check_token_ticker(chain_config, current_height, &issuance_data.token_ticker)?;

// Check decimals
ensure!(
Expand Down Expand Up @@ -150,6 +153,7 @@ pub fn check_tokens_data(
token_data: &TokenData,
tx: &Transaction,
source_block_id: Id<Block>,
source_block_height: BlockHeight,
) -> Result<(), TokensError> {
match token_data {
TokenData::TokenTransfer(transfer) => {
Expand All @@ -159,9 +163,13 @@ pub fn check_tokens_data(
);
Ok(())
}
TokenData::TokenIssuance(issuance) => check_tokens_issuance_data_v0(chain_config, issuance)
.map_err(|err| TokensError::IssueError(err, tx.get_id(), source_block_id)),
TokenData::NftIssuance(issuance) => check_nft_issuance_data(chain_config, issuance)
.map_err(|err| TokensError::IssueError(err, tx.get_id(), source_block_id)),
TokenData::TokenIssuance(issuance) => {
check_tokens_issuance_data_v0(chain_config, source_block_height, issuance)
.map_err(|err| TokensError::IssueError(err, tx.get_id(), source_block_id))
}
TokenData::NftIssuance(issuance) => {
check_nft_issuance_data(chain_config, source_block_height, issuance)
.map_err(|err| TokensError::IssueError(err, tx.get_id(), source_block_id))
}
}
}
Loading