use alloc::string::ToString;
use keetanetwork_account::AccountError;
use keetanetwork_asn1::Asn1Error;
use keetanetwork_crypto::error::CryptoError;
use keetanetwork_crypto::hash::BlockHash;
use keetanetwork_error::KeetaNetError;
use keetanetwork_utils::impl_source_error_from;
use snafu::Snafu;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockField {
Version,
Network,
Account,
Previous,
Signer,
Date,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InfoField {
Name,
Description,
Metadata,
}
impl core::fmt::Display for BlockField {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = match self {
BlockField::Version => "version",
BlockField::Network => "network",
BlockField::Account => "account",
BlockField::Previous => "previous",
BlockField::Signer => "signer",
BlockField::Date => "date",
};
write!(f, "{name}")
}
}
impl core::fmt::Display for InfoField {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = match self {
InfoField::Name => "name",
InfoField::Description => "description",
InfoField::Metadata => "metadata",
};
write!(f, "{name}")
}
}
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum BlockError {
#[snafu(display("ASN.1 codec error: {source}"))]
Codec {
source: Asn1Error,
},
#[snafu(display("account error: {source}"))]
Account {
source: AccountError,
},
#[snafu(display("crypto error: {source}"))]
Crypto {
source: CryptoError,
},
#[snafu(display("certificate error: {source}"))]
Certificate {
source: keetanetwork_x509::error::CertificateError,
},
#[snafu(display("unsupported block version"))]
InvalidVersion,
#[snafu(display("block references itself as previous"))]
PreviousSelf,
#[snafu(display("network ID must be a positive number"))]
NegativeNetwork,
#[snafu(display("subnet ID must be a positive number"))]
NegativeSubnet,
#[snafu(display("unknown network ID"))]
UnknownNetwork,
#[snafu(display("cannot create a block for a multisig account"))]
MultisigAccountForbidden,
#[snafu(display("missing required block field: {field}"))]
MissingField {
field: BlockField,
},
#[snafu(display("account and signer must not both be present when equal"))]
RedundantAccountField,
#[snafu(display("block has not been signed"))]
SignatureRequired,
#[snafu(display("signer count {expected} does not match signature count {actual}"))]
SignatureCountMismatch {
expected: usize,
actual: usize,
},
#[snafu(display("unable to validate signature at index {index} for block {hash}"))]
InvalidSignature {
index: usize,
hash: BlockHash,
},
#[snafu(display("signature must be exactly 64 bytes, got {length}"))]
InvalidSignatureLength {
length: usize,
},
#[snafu(display("signature sequence must contain more than one signature"))]
InvalidSignatureSequence,
#[snafu(display("block bytes do not match recalculated bytes"))]
RecalculatedBytesMismatch,
#[snafu(display("V1 blocks support only a single signer and signature"))]
V1SingleSignerOnly,
#[snafu(display("V1 block purpose must be generic"))]
V1PurposeInvalid,
#[snafu(display("idempotent key length {length} exceeds maximum {max}"))]
IdempotentTooLong {
length: usize,
max: usize,
},
#[snafu(display("FEE purpose block contains non-SEND operation at index {operation_index}"))]
FeePurposeRequiresSend {
operation_index: usize,
},
#[snafu(display("invalid block purpose"))]
InvalidPurpose,
#[snafu(display("invalid operation type"))]
InvalidOperationType,
#[snafu(display("invalid adjust method"))]
InvalidAdjustMethod,
#[snafu(display("cannot use the SET adjust method for this operation"))]
AdjustMethodSetForbidden,
#[snafu(display("multisig signer depth {depth} exceeds maximum {max}"))]
MultisigSignerDepthExceeded {
depth: u64,
max: u64,
},
#[snafu(display("multisig signer count {count} out of range [1, {max}]"))]
MultisigSignerCountInvalid {
count: u64,
max: u64,
},
#[snafu(display("duplicate multisig signer"))]
MultisigSignerDuplicate,
#[snafu(display("multisig quorum out of range"))]
MultisigQuorumInvalid,
#[snafu(display("malformed multisig signer structure"))]
MalformedSigner,
#[snafu(display("amount cannot be negative"))]
AmountBelowZero,
#[snafu(display("supply exceeds maximum value"))]
SupplyInvalid,
#[snafu(display("token field must reference a token account"))]
TokenFieldNotToken,
#[snafu(display("token accounts cannot use this operation"))]
TokenOperationForbidden,
#[snafu(display("only token accounts can use this operation"))]
TokenAccountRequired,
#[snafu(display("cannot send a token to a token account different from itself"))]
TokenReceiveDiffers,
#[snafu(display("external length {length} exceeds maximum {max}"))]
ExternalTooLong {
length: usize,
max: usize,
},
#[snafu(display("external has invalid characters"))]
ExternalInvalid,
#[snafu(display("external is required when using SEND"))]
ExternalMissing,
#[snafu(display("cannot use forward field to send to self"))]
ForwardToSelf,
#[snafu(display("cannot use forward field without exact being set to true"))]
ForwardRequiresExact,
#[snafu(display("identifier accounts cannot be used for delegation"))]
IdentifierDelegationForbidden,
#[snafu(display("SET_REP may only be used once per block"))]
MultipleSetRep,
#[snafu(display("{field} does not fit the required format"))]
InfoFieldInvalid {
field: InfoField,
},
#[snafu(display("identifier accounts need default permissions in SET_INFO"))]
DefaultPermissionRequired,
#[snafu(display("only identifier accounts may use this construct"))]
IdentifierAccountRequired,
#[snafu(display("requested identifier is not valid"))]
IdentifierInvalid,
#[snafu(display("invalid create identifier arguments"))]
InvalidCreateIdentifierArguments,
#[snafu(display("invalid principal for MODIFY_PERMISSIONS"))]
InvalidPrincipal,
#[snafu(display("method must be SET when permissions are absent"))]
PermissionsRequireSet,
#[snafu(display("cannot set admin or higher with a target specified"))]
AdminWithTarget,
#[snafu(display("cannot use delegation for these permission flags"))]
DelegationForbidden,
#[snafu(display("cannot have a SET after another change with the same target in MODIFY_PERMISSIONS"))]
DuplicatePermissionModification,
#[snafu(display("cannot mix base permission flags with different groups"))]
PermissionsCannotMix,
#[snafu(display("external permission size {size} exceeds maximum offset {max}"))]
PermissionsExternalOffsetTooLarge {
size: u64,
max: u64,
},
#[snafu(display("permissions are not valid as default permissions"))]
PermissionsInvalidDefault,
#[snafu(display("incorrect entity for permission flags"))]
PermissionsInvalidEntity,
#[snafu(display("incorrect principal for permission flags"))]
PermissionsInvalidPrincipal,
#[snafu(display("incorrect target for permission flags"))]
PermissionsInvalidTarget,
#[snafu(display("cannot set default permissions with external permissions"))]
PermissionsExternalDefaultForbidden,
#[snafu(display("invalid certificate value for MANAGE_CERTIFICATE"))]
InvalidCertificateValue,
#[snafu(display("certificate subject does not match the block account"))]
CertificateSubjectMismatch,
#[snafu(display("intermediate certificates can only be provided when adding"))]
IntermediateCertificatesOnlyAdd,
#[snafu(display("cannot operate on the same certificate twice"))]
DuplicateCertificateOperation,
}
impl_source_error_from!(BlockError, {
Asn1Error => Codec,
AccountError => Account,
CryptoError => Crypto,
});
impl_source_error_from!(BlockError, {
keetanetwork_x509::error::CertificateError => Certificate,
});
impl BlockError {
pub fn code(&self) -> Option<&'static str> {
let code = match self {
BlockError::InvalidVersion => "BLOCK_INVALID_VERSION",
BlockError::PreviousSelf => "BLOCK_PREVIOUS_SELF",
BlockError::MultisigAccountForbidden => "BLOCK_NO_MULTISIG_OP",
BlockError::SignatureRequired => "BLOCK_SIGNATURE_REQUIRED",
BlockError::SignatureCountMismatch { .. } | BlockError::V1SingleSignerOnly => "BLOCK_INVALID_SIGNER",
BlockError::InvalidSignature { .. } | BlockError::RecalculatedBytesMismatch => "BLOCK_INVALID_SIGNATURE",
BlockError::IdempotentTooLong { .. } => "BLOCK_INVALID_IDEMPOTENT_LENGTH",
BlockError::FeePurposeRequiresSend { .. } | BlockError::V1PurposeInvalid => {
"BLOCK_INVALID_PURPOSE_VALIDATION"
}
BlockError::InvalidOperationType => "BLOCK_INVALID_TYPE",
BlockError::MultisigSignerDepthExceeded { .. } => "BLOCK_INVALID_MULTISIG_SIGNER_DEPTH",
BlockError::MultisigSignerCountInvalid { .. } => "BLOCK_INVALID_MULTISIG_SIGNER_COUNT",
BlockError::MultisigSignerDuplicate => "BLOCK_INVALID_MULTISIG_SIGNER_DUPLICATE",
BlockError::MultisigQuorumInvalid => "BLOCK_INVALID_MULTISIG_QUORUM",
BlockError::AmountBelowZero => "BLOCK_AMOUNT_BELOW_ZERO",
BlockError::SupplyInvalid => "BLOCK_SUPPLY_INVALID",
BlockError::TokenFieldNotToken => "BLOCK_CANNOT_SEND_NON_TOKEN",
BlockError::TokenOperationForbidden => "BLOCK_NO_TOKEN_OP",
BlockError::TokenAccountRequired => "BLOCK_ONLY_TOKEN_OP",
BlockError::TokenReceiveDiffers => "BLOCK_TOKEN_RECEIVE_DIFFERS",
BlockError::ExternalTooLong { .. } => "BLOCK_EXTERNAL_TOO_LONG",
BlockError::ExternalInvalid => "BLOCK_EXTERNAL_INVALID",
BlockError::ExternalMissing => "BLOCK_EXTERNAL_MISSING",
BlockError::ForwardToSelf => "BLOCK_CANNOT_FORWARD_TO_SELF",
BlockError::ForwardRequiresExact => "BLOCK_EXACT_TRUE_WHEN_FORWARDING",
BlockError::IdentifierDelegationForbidden => "BLOCK_NO_IDENTIFIER_OP",
BlockError::MultipleSetRep => "BLOCK_NO_MULTIPLE_SET_REP",
BlockError::InfoFieldInvalid { .. } | BlockError::PermissionsExternalDefaultForbidden => {
"BLOCK_GENERAL_FIELD_INVALID"
}
BlockError::DefaultPermissionRequired => "BLOCK_IDENTIFIER_NEED_DEFAULT_PERMISSIONS",
BlockError::IdentifierAccountRequired => "BLOCK_ONLY_IDENTIFIER_OP",
BlockError::IdentifierInvalid => "BLOCK_IDENTIFIER_INVALID",
BlockError::InvalidCreateIdentifierArguments => "BLOCK_INVALID_CREATE_IDENTIFIER_ARGS",
BlockError::InvalidPrincipal => "BLOCK_INVALID_PRINCIPAL",
BlockError::AdminWithTarget => "BLOCK_NO_ADMIN_ON_TARGET",
BlockError::DelegationForbidden => "BLOCK_NO_DELEGATE_ADMIN",
BlockError::DuplicatePermissionModification => "BLOCK_NO_MODIFY_PERMISSION_DUPE",
BlockError::PermissionsCannotMix => "PERMISSIONS_CANNOT_MIX_FLAGS_AND_TYPES",
BlockError::PermissionsExternalOffsetTooLarge { .. } => "PERMISSIONS_EXTERNAL_OFFSET_TOO_LARGE",
BlockError::PermissionsInvalidDefault => "BLOCK_PERMISSIONS_INVALID_DEFAULT",
BlockError::PermissionsInvalidEntity => "BLOCK_PERMISSIONS_INVALID_ENTITY",
BlockError::PermissionsInvalidPrincipal => "BLOCK_PERMISSIONS_INVALID_PRINCIPAL",
BlockError::PermissionsInvalidTarget => "BLOCK_PERMISSIONS_INVALID_TARGET",
BlockError::InvalidCertificateValue => "BLOCK_INVALID_CERTIFICATE_VALUE",
BlockError::CertificateSubjectMismatch => "BLOCK_CERTIFICATE_SUBJECT_MISMATCH",
BlockError::IntermediateCertificatesOnlyAdd => "BLOCK_INTERMEDIATE_CERTIFICATES_ONLY_ADD",
BlockError::DuplicateCertificateOperation => "BLOCK_NO_DUPLICATE_CERTIFICATE_OPERATION",
_ => return None,
};
Some(code)
}
}
impl From<BlockError> for KeetaNetError {
fn from(error: BlockError) -> Self {
if let Some(code) = error.code() {
KeetaNetError::Code { code: code.to_string(), message: error.to_string() }
} else {
KeetaNetError::Unknown { msg: error.to_string() }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_mapping() {
assert_eq!(BlockError::InvalidVersion.code(), Some("BLOCK_INVALID_VERSION"));
assert_eq!(BlockError::PreviousSelf.code(), Some("BLOCK_PREVIOUS_SELF"));
assert_eq!(BlockError::NegativeNetwork.code(), None);
}
#[test]
fn test_keetanet_error_bridge() {
let bridged = KeetaNetError::from(BlockError::PreviousSelf);
assert!(matches!(bridged, KeetaNetError::Code { code, .. } if code == "BLOCK_PREVIOUS_SELF"));
let unknown = KeetaNetError::from(BlockError::NegativeNetwork);
assert!(matches!(unknown, KeetaNetError::Unknown { .. }));
}
#[test]
fn test_source_conversions() {
let codec_error = Asn1Error::RasnError { reason: "boom".to_string() };
assert!(matches!(BlockError::from(codec_error), BlockError::Codec { .. }));
assert!(matches!(BlockError::from(AccountError::InvalidKeyType), BlockError::Account { .. }));
assert!(matches!(BlockError::from(CryptoError::InvalidInput), BlockError::Crypto { .. }));
}
}