use alloc::string::ToString;
use keetanetwork_account::AccountError;
use keetanetwork_asn1::Asn1Error;
use keetanetwork_block::BlockError;
use keetanetwork_crypto::error::CryptoError;
use keetanetwork_error::KeetaNetError;
use keetanetwork_utils::impl_source_error_from;
use snafu::Snafu;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VoteField {
Serial,
Issuer,
Validity,
}
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum VoteError {
#[snafu(display("ASN.1 error: {source}"))]
Asn1 {
source: Asn1Error,
},
#[snafu(display("account error: {source}"))]
Account {
source: AccountError,
},
#[snafu(display("crypto error: {source}"))]
Crypto {
source: CryptoError,
},
#[snafu(display("block error: {source}"))]
Block {
source: BlockError,
},
#[snafu(display("vote subject serial does not match certificate serial"))]
SerialMismatch,
#[snafu(display("vote certificate version must be 3"))]
InvalidVersion,
#[snafu(display("vote bytes are not a recognized construction"))]
InvalidConstruction,
#[snafu(display("vote JSON is not a valid construction"))]
InvalidConstructionJson,
#[snafu(display("vote signature did not verify"))]
SignatureInvalid,
#[snafu(display("vote has expired"))]
Expired,
#[snafu(display("vote validity range is invalid"))]
InvalidValidity,
#[snafu(display("vote was issued in the future"))]
MomentBeforeValidityFrom,
#[snafu(display("validation moment must be provided in no_std builds"))]
MissingMoment,
#[snafu(display("malformed vote staple wrapper"))]
MalformedStaple,
#[snafu(display("vote staple must contain at least one block"))]
StapleBlocksAtLeastOne,
#[snafu(display("vote staple must contain at least one vote"))]
StapleVotesAtLeastOne,
#[snafu(display("malformed vote staple element: {what}"))]
MalformedStapleElement {
what: &'static str,
},
#[snafu(display("votes within a staple disagree on block count"))]
StapleBlockCountMismatch,
#[snafu(display("vote references a block hash not in the staple"))]
StapleMissingBlock,
#[snafu(display("votes within a staple disagree on block ordering"))]
StapleBlockOrderMismatch,
#[snafu(display("vote staple has duplicate issuer"))]
StapleDuplicateIssuer,
#[snafu(display("vote staple mixes permanent and temporary votes"))]
StaplePermanenceMismatch,
#[snafu(display("invalid vote staple construction"))]
StapleInvalidConstruction,
#[snafu(display("vote builder is missing a required field: {field:?}"))]
BuilderMissingField {
field: VoteField,
},
#[snafu(display("invalid block reference for vote builder"))]
BuilderInvalidBlockType,
#[snafu(display("invalid fee supplied to vote builder"))]
BuilderInvalidFee,
#[snafu(display("malformed vote wrapper"))]
MalformedWrapper,
#[snafu(display("malformed vote (tbs slot must be a sequence)"))]
MalformedVoteWrapper,
#[snafu(display("malformed vote certificate body"))]
MalformedVoteContent,
#[snafu(display("vote certificate has extra trailing data"))]
MalformedVoteContentExtraData,
#[snafu(display("malformed vote version field"))]
MalformedVoteVersion,
#[snafu(display("malformed vote serial field"))]
MalformedVoteSerial,
#[snafu(display("malformed vote signature algorithm field"))]
MalformedVoteSignatureInformation,
#[snafu(display("malformed vote issuer information"))]
MalformedVoteIssuerInformation,
#[snafu(display("malformed vote subject information"))]
MalformedVoteSubjectInformation,
#[snafu(display("malformed vote validity information"))]
MalformedVoteValidityInformation,
#[snafu(display("malformed vote extensions field"))]
MalformedVoteExtensions,
#[snafu(display("malformed vote extension contents"))]
MalformedVoteExtensionsData,
#[snafu(display("malformed vote extension element"))]
MalformedVoteExtensionsValue,
#[snafu(display("malformed vote extension OID"))]
MalformedVoteExtensionsValueOid,
#[snafu(display("malformed vote extension critical flag"))]
MalformedVoteExtensionsValueCritical,
#[snafu(display("unknown critical vote extension"))]
MalformedVoteExtensionsValueCriticalType,
#[snafu(display("vote signature algorithm does not match wrapper"))]
MalformedVoteSignatureSchemeDoesNotMatchWrapper,
#[snafu(display("vote signature algorithm does not match issuer"))]
MalformedVoteSignatureSchemeDoesNotMatchIssuer,
#[snafu(display("unsupported vote signature scheme"))]
MalformedVoteSignatureUnsupportedScheme,
#[snafu(display("vote ECDSA curve is not supported"))]
MalformedVoteSignatureSchemeEcdsaInvalidCurve,
#[snafu(display("malformed subject public key info"))]
MalformedVoteSubjectPublicKeyInformation,
#[snafu(display("malformed vote signature value"))]
MalformedVoteSignatureValue,
#[snafu(display("vote contains no block-hash extension"))]
MalformedVoteNoBlocksFound,
#[snafu(display("malformed DN: not a sequence"))]
MalformedFindRdnInvalidType,
#[snafu(display("malformed DN: empty"))]
MalformedFindRdnMustHaveOne,
#[snafu(display("malformed DN: element not well-formed"))]
MalformedFindRdnPartWellFormed,
#[snafu(display("malformed DN: element not a set"))]
MalformedFindRdnMustBeSet,
#[snafu(display("malformed DN: element name not an OID"))]
MalformedFindRdnTypeMustBeOid,
#[snafu(display("malformed hashData extension input"))]
MalformedHashesFromVoteInvalidInput,
#[snafu(display("hashData extension is not a context-specific tag"))]
MalformedHashesFromVoteInvalidType,
#[snafu(display("hashData extension has wrong context tag"))]
MalformedHashesFromVoteInvalidContextSpecific,
#[snafu(display("hashData extension data is not a sequence"))]
MalformedHashesFromVoteDataHashDataMustBeSequence,
#[snafu(display("hashData extension expected exactly two items"))]
MalformedHashesFromVoteDataNotTwoItems,
#[snafu(display("hashData extension is missing the hash algorithm OID"))]
MalformedHashesFromVoteDataNeedsOid,
#[snafu(display("hashData extension uses an unsupported hash algorithm"))]
MalformedHashesFromVoteDataUnsupportedHashFunc,
#[snafu(display("hashData extension blocks element must be a sequence"))]
MalformedHashesFromVoteDataSecondMustBeSequence,
#[snafu(display("hashData extension contains a non-octet-string block hash"))]
MalformedHashesFromVoteDataUnsupportedHashType,
#[snafu(display("fee amount cannot be negative"))]
MalformedFeesAmount,
#[snafu(display("fees extension is not valid ASN.1"))]
MalformedFeesFromVoteInvalidInput,
#[snafu(display("permanent votes cannot carry fees"))]
MalformedFeesInPermanentVote,
#[snafu(display("fees extension quote flag does not match vote variant"))]
MalformedFeesQuoteInvalid,
#[snafu(display("fees extension has inconsistent or missing quote flag"))]
MalformedFeesInvalidQuoteValue,
#[snafu(display("fees extension is missing its kind discriminator"))]
MalformedFeesKindMissing,
#[snafu(display("multiple-fee extension array is empty"))]
MalformedFeesMultipleFeeEmpty,
#[snafu(display("fee payTo is not an account or storage address"))]
MalformedFeesPayToInvalid,
#[snafu(display("fee token field is not a token account"))]
MalformedFeesTokenNotToken,
#[snafu(display("attempted to construct a Vote from a VoteQuote"))]
FeeIsQuote,
#[snafu(display("attempted to construct a VoteQuote from a Vote"))]
FeeNotQuote,
#[snafu(display("quote vote must carry fees"))]
FeeQuoteMissingFees,
#[snafu(display("vote bytes are not canonical DER"))]
MalformedNonCanonicalEncoding,
}
impl_source_error_from!(VoteError, {
Asn1Error => Asn1,
AccountError => Account,
CryptoError => Crypto,
BlockError => Block,
});
impl VoteError {
pub fn code(&self) -> Option<&'static str> {
let code = match self {
VoteError::SerialMismatch => "VOTE_SERIAL_MISMATCH",
VoteError::InvalidVersion => "VOTE_INVALID_VERSION",
VoteError::InvalidConstruction => "VOTE_INVALID_CONSTRUCTION",
VoteError::InvalidConstructionJson => "VOTE_INVALID_CONSTRUCTION_JSON",
VoteError::SignatureInvalid => "VOTE_SIGNATURE_INVALID",
VoteError::Expired => "VOTE_EXPIRED",
VoteError::InvalidValidity => "VOTE_INVALID_VALIDITY",
VoteError::MomentBeforeValidityFrom => "VOTE_MOMENT_BEFORE_VALIDITY_FROM",
VoteError::MissingMoment => "VOTE_MISSING_MOMENT",
VoteError::StapleInvalidConstruction => "VOTE_STAPLE_INVALID_CONSTRUCTION",
VoteError::StapleBlockCountMismatch => "VOTE_STAPLE_ALL_VOTES_MUST_HAVE_SAME_BLOCKS_COUNT",
VoteError::StapleMissingBlock => "VOTE_STAPLE_ALL_VOTES_MUST_HAVE_SAME_BLOCKS_MISSING",
VoteError::StapleBlockOrderMismatch => "VOTE_STAPLE_ALL_VOTES_MUST_HAVE_SAME_BLOCKS_ORDER",
VoteError::StapleDuplicateIssuer => "VOTE_STAPLE_DUPLICATE_VOTE_ISSUER",
VoteError::StaplePermanenceMismatch => "VOTE_STAPLE_PERMANENCE_MISMATCH",
VoteError::BuilderMissingField { field: VoteField::Serial } => "VOTE_BUILDER_INVALID_SERIAL",
VoteError::BuilderMissingField { field: VoteField::Issuer } => "VOTE_BUILDER_INVALID_CONSTRUCTION",
VoteError::BuilderMissingField { field: VoteField::Validity } => "VOTE_BUILDER_INVALID_VALID_TO_FROM",
VoteError::BuilderInvalidBlockType => "VOTE_BUILDER_INVALID_BLOCK_TYPE",
VoteError::BuilderInvalidFee => "VOTE_BUILDER_INVALID_FEE",
VoteError::MalformedWrapper => "VOTE_MALFORMED_WRAPPER",
VoteError::MalformedVoteWrapper => "VOTE_MALFORMED_VOTE_WRAPPER",
VoteError::MalformedVoteContent => "VOTE_MALFORMED_VOTE_CONTENT",
VoteError::MalformedVoteContentExtraData => "VOTE_MALFORMED_VOTE_CONTENT_EXTRA_DATA",
VoteError::MalformedVoteVersion => "VOTE_MALFORMED_VOTE_VERSION",
VoteError::MalformedVoteSerial => "VOTE_MALFORMED_VOTE_SERIAL",
VoteError::MalformedVoteSignatureInformation => "VOTE_MALFORMED_VOTE_SIGNATURE_INFORMATION",
VoteError::MalformedVoteIssuerInformation => "VOTE_MALFORMED_VOTE_ISSUER_INFORMATION",
VoteError::MalformedVoteSubjectInformation => "VOTE_MALFORMED_VOTE_SUBJECT_INFORMATION",
VoteError::MalformedVoteValidityInformation => "VOTE_MALFORMED_VOTE_VALIDITY_INFORMATION",
VoteError::MalformedVoteExtensions => "VOTE_MALFORMED_VOTE_EXTENSIONS",
VoteError::MalformedVoteExtensionsData => "VOTE_MALFORMED_VOTE_EXTENSIONS_DATA",
VoteError::MalformedVoteExtensionsValue => "VOTE_MALFORMED_VOTE_EXTENSIONS_VALUE",
VoteError::MalformedVoteExtensionsValueOid => "VOTE_MALFORMED_VOTE_EXTENSIONS_VALUE_OID",
VoteError::MalformedVoteExtensionsValueCritical => "VOTE_MALFORMED_VOTE_EXTENSIONS_VALUE_CRITICAL",
VoteError::MalformedVoteExtensionsValueCriticalType => "VOTE_MALFORMED_VOTE_EXTENSIONS_VALUE_CRITICAL_TYPE",
VoteError::MalformedVoteSignatureSchemeDoesNotMatchWrapper => {
"VOTE_MALFORMED_VOTE_SIGNATURE_SCHEME_DOES_NOT_MATCH_WRAPPER"
}
VoteError::MalformedVoteSignatureSchemeDoesNotMatchIssuer => {
"VOTE_MALFORMED_VOTE_SIGNATURE_SCHEME_DOES_NOT_MATCH_ISSUER"
}
VoteError::MalformedVoteSignatureUnsupportedScheme => "VOTE_MALFORMED_VOTE_SIGNATURE_UNSUPPORTED_SCHEME",
VoteError::MalformedVoteSignatureSchemeEcdsaInvalidCurve => {
"VOTE_MALFORMED_VOTE_SIGNATURE_SCHEME_ECDSA_INVALID_CURVE"
}
VoteError::MalformedVoteSubjectPublicKeyInformation => "VOTE_MALFORMED_VOTE_SUBJECT_PUBLIC_KEY_INFORMATION",
VoteError::MalformedVoteSignatureValue => "VOTE_MALFORMED_VOTE_SIGNATURE_VALUE",
VoteError::MalformedVoteNoBlocksFound => "VOTE_MALFORMED_VOTE_NO_BLOCKS_FOUND",
VoteError::MalformedStaple => "VOTE_MALFORMED_STAPLE",
VoteError::MalformedStapleElement { what: "blocks" } => "VOTE_MALFORMED_STAPLE_BLOCKS",
VoteError::MalformedStapleElement { what: "votes" } => "VOTE_MALFORMED_STAPLE_VOTES",
VoteError::StapleBlocksAtLeastOne => "VOTE_MALFORMED_STAPLE_BLOCKS_AT_LEAST_ONE",
VoteError::StapleVotesAtLeastOne => "VOTE_MALFORMED_STAPLE_VOTES_AT_LEAST_ONE",
VoteError::MalformedFindRdnInvalidType => "VOTE_MALFORMED_FIND_RDN_INVALID_TYPE",
VoteError::MalformedFindRdnMustHaveOne => "VOTE_MALFORMED_FIND_RDN_MUST_HAVE_ONE",
VoteError::MalformedFindRdnPartWellFormed => "VOTE_MALFORMED_FIND_RDN_PART_WELL_FORMED",
VoteError::MalformedFindRdnMustBeSet => "VOTE_MALFORMED_FIND_RDN_MUST_BE_SET",
VoteError::MalformedFindRdnTypeMustBeOid => "VOTE_MALFORMED_FIND_RDN_TYPE_MUST_BE_OID",
VoteError::MalformedHashesFromVoteInvalidInput => "VOTE_MALFORMED_HASHES_FROM_VOTE_INVALID_INPUT",
VoteError::MalformedHashesFromVoteInvalidType => "VOTE_MALFORMED_HASHES_FROM_VOTE_INVALID_TYPE",
VoteError::MalformedHashesFromVoteInvalidContextSpecific => {
"VOTE_MALFORMED_HASHES_FROM_VOTE_INVALID_CONTEXT_SPECIFIC"
}
VoteError::MalformedHashesFromVoteDataHashDataMustBeSequence => {
"VOTE_MALFORMED_HASHES_FROM_VOTE_DATA_HASH_DATA_MUST_BE_SEQUENCE"
}
VoteError::MalformedHashesFromVoteDataNotTwoItems => "VOTE_MALFORMED_HASHES_FROM_VOTE_DATA_NOT_TWO_ITEMS",
VoteError::MalformedHashesFromVoteDataNeedsOid => "VOTE_MALFORMED_HASHES_FROM_VOTE_DATA_NEEDS_OID",
VoteError::MalformedHashesFromVoteDataUnsupportedHashFunc => {
"VOTE_MALFORMED_HASHES_FROM_VOTE_DATA_UNSUPPORTED_HASH_FUNC"
}
VoteError::MalformedHashesFromVoteDataSecondMustBeSequence => {
"VOTE_MALFORMED_HASHES_FROM_VOTE_DATA_SECOND_MUST_BE_SEQUENCE"
}
VoteError::MalformedHashesFromVoteDataUnsupportedHashType => {
"VOTE_MALFORMED_HASHES_FROM_VOTE_DATA_UNSUPPORTED_HASH_TYPE"
}
VoteError::MalformedFeesAmount => "VOTE_MALFORMED_FEES_AMOUNT",
VoteError::MalformedFeesFromVoteInvalidInput => "VOTE_MALFORMED_FEES_FROM_VOTE_INVALID_INPUT",
VoteError::MalformedFeesInPermanentVote => "VOTE_MALFORMED_FEES_IN_PERMANENT_VOTE",
VoteError::MalformedFeesQuoteInvalid => "VOTE_MALFORMED_FEES_QUOTE_INVALID",
VoteError::MalformedFeesInvalidQuoteValue => "VOTE_MALFORMED_FEES_INVALID_QUOTE_VALUE",
VoteError::MalformedFeesKindMissing => "VOTE_MALFORMED_FEES_KIND_MISSING",
VoteError::MalformedFeesMultipleFeeEmpty => "VOTE_MALFORMED_FEES_MULTIPLE_FEE_EMPTY",
VoteError::MalformedFeesPayToInvalid => "VOTE_MALFORMED_FEES_PAY_TO_INVALID",
VoteError::MalformedFeesTokenNotToken => "VOTE_MALFORMED_FEES_TOKEN_NOT_TOKEN",
VoteError::FeeIsQuote => "VOTE_FEE_IS_QUOTE",
VoteError::FeeNotQuote => "VOTE_FEE_NOT_QUOTE",
VoteError::FeeQuoteMissingFees => "VOTE_FEE_QUOTE_MISSING_FEES",
_ => return None,
};
Some(code)
}
}
impl From<VoteError> for KeetaNetError {
fn from(error: VoteError) -> 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!(VoteError::SerialMismatch.code(), Some("VOTE_SERIAL_MISMATCH"));
assert_eq!(VoteError::Expired.code(), Some("VOTE_EXPIRED"));
assert_eq!(VoteError::FeeIsQuote.code(), Some("VOTE_FEE_IS_QUOTE"));
assert_eq!(VoteError::Crypto { source: CryptoError::InvalidInput }.code(), None);
}
#[test]
fn test_staple_element_codes_split() {
assert_eq!(VoteError::MalformedStapleElement { what: "blocks" }.code(), Some("VOTE_MALFORMED_STAPLE_BLOCKS"));
assert_eq!(VoteError::MalformedStapleElement { what: "votes" }.code(), Some("VOTE_MALFORMED_STAPLE_VOTES"));
}
#[test]
fn test_keetanet_error_bridge() {
let bridged = KeetaNetError::from(VoteError::SerialMismatch);
assert!(matches!(bridged, KeetaNetError::Code { code, .. } if code == "VOTE_SERIAL_MISMATCH"));
let unknown = KeetaNetError::from(VoteError::Crypto { source: CryptoError::InvalidInput });
assert!(matches!(unknown, KeetaNetError::Unknown { .. }));
}
#[test]
fn test_source_conversions() {
assert!(matches!(VoteError::from(Asn1Error::InvalidVoteVersion), VoteError::Asn1 { .. }));
assert!(matches!(VoteError::from(AccountError::InvalidKeyType), VoteError::Account { .. }));
assert!(matches!(VoteError::from(CryptoError::InvalidInput), VoteError::Crypto { .. }));
}
}