From 9e2442c42697358f987ed39dea49cadb93dc452e Mon Sep 17 00:00:00 2001 From: Jeff Bencin Date: Wed, 7 Jun 2023 16:46:19 -0400 Subject: [PATCH] fix: Should fix failures reading subnet contract version --- core-contracts/contracts/multi-miner.clar | 6 +- core-contracts/contracts/subnet.clar | 10 +- .../stacks-node/src/burnchains/l1_events.rs | 131 ++++++++++++++---- testnet/stacks-node/src/burnchains/mod.rs | 22 ++- 4 files changed, 131 insertions(+), 38 deletions(-) diff --git a/core-contracts/contracts/multi-miner.clar b/core-contracts/contracts/multi-miner.clar index 787e9e926..e4b8b00f0 100644 --- a/core-contracts/contracts/multi-miner.clar +++ b/core-contracts/contracts/multi-miner.clar @@ -22,9 +22,9 @@ ;; Minimun version of subnet contract required (define-constant SUBNET_CONTRACT_VERSION_MIN { - major: 2, - minor: 0, - patch: 0, + major: u2, + minor: u0, + patch: u0, }) ;; Return error if subnet contract version not supported diff --git a/core-contracts/contracts/subnet.clar b/core-contracts/contracts/subnet.clar index ce1d93e08..528732faf 100644 --- a/core-contracts/contracts/subnet.clar +++ b/core-contracts/contracts/subnet.clar @@ -7,11 +7,11 @@ ;; NOTE: Versioning was added as of `2.0.0` ;; NOTE: Contract should be deployed with name matching version here (define-constant VERSION { - major: 2, - minor: 0, - patch: 0, - prerelease: "", - metadata: "" + major: u2, + minor: u0, + patch: u0, + prerelease: none, + metadata: none }) ;; Error codes diff --git a/testnet/stacks-node/src/burnchains/l1_events.rs b/testnet/stacks-node/src/burnchains/l1_events.rs index 004ee5be2..fafd7db3f 100644 --- a/testnet/stacks-node/src/burnchains/l1_events.rs +++ b/testnet/stacks-node/src/burnchains/l1_events.rs @@ -1,7 +1,13 @@ +use std::fmt; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Instant; +use clarity::vm::errors::{Error as ClarityError, RuntimeErrorType as ClarityRuntimeError}; +use clarity::vm::types::{ + SequenceSubtype, StringSubtype, StringUTF8Length, TupleTypeSignature, TypeSignature, + Value as ClarityValue, +}; use stacks::burnchains::db::BurnchainDB; use stacks::burnchains::events::NewBlock; use stacks::burnchains::indexer::BurnchainIndexer; @@ -55,19 +61,61 @@ pub struct L1Controller { /// Semver version of a Clarity contract #[derive(Deserialize, Serialize)] pub struct ContractVersion { - major: u32, - minor: u32, - patch: u32, + major: u128, + minor: u128, + patch: u128, prerelease: Option, metadata: Option, } +impl fmt::Display for ContractVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?; + if let Some(pre) = &self.prerelease { + write!(f, "-{pre}")?; + } + if let Some(meta) = &self.metadata { + write!(f, "+{meta}")?; + } + Ok(()) + } +} + +impl TryFrom for ContractVersion { + type Error = ClarityError; + + fn try_from(value: ClarityValue) -> Result { + // FIXME: Clean up this mess. This shouldn't `panic!()` ever! + match value { + ClarityValue::Tuple(t) => Ok(ContractVersion { + major: t.get("major")?.clone().expect_u128(), + minor: t.get("minor")?.clone().expect_u128(), + patch: t.get("patch")?.clone().expect_u128(), + prerelease: t + .get("prerelease")? + .clone() + .expect_optional() + .map(|v| v.expect_ascii()), + metadata: t + .get("metadata")? + .clone() + .expect_optional() + .map(|v| v.expect_ascii()), + }), + _ => Err(ClarityError::Runtime( + ClarityRuntimeError::ParseError("Expected Tuple".into()), + None, + )), + } + } +} + /// Response from read-only function #[derive(Deserialize, Serialize)] pub struct GetVersionResponse { okay: bool, /// Response will contain `result` on success - result: Option, + result: Option, /// Response will contain `cause` on failure cause: Option, } @@ -275,8 +323,7 @@ impl L1Controller { .json(&body) .send()? .error_for_status()? - .json::() - .map_err(Error::from)?; + .json::()?; if !response.okay { let message = response @@ -285,53 +332,85 @@ impl L1Controller { return Err(Error::RPCError(message)); } - match response.result { - Some(r) => Ok(r), - None => Err(Error::RPCError("Empty result".to_string())), - } + let result = response + .result + .ok_or(Error::RPCError("Empty result".to_string()))? + .strip_prefix("0x") + .unwrap() // FIXME + .to_string(); + + let typesig = TypeSignature::TupleType( + TupleTypeSignature::try_from(vec![ + ("major".into(), TypeSignature::UIntType), + ("minor".into(), TypeSignature::UIntType), + ("patch".into(), TypeSignature::UIntType), + ( + "prerelease".into(), + TypeSignature::OptionalType(Box::new(TypeSignature::SequenceType( + SequenceSubtype::StringType(StringSubtype::UTF8( + StringUTF8Length::try_from(64usize).unwrap(), + )), + ))), + ), + ( + "metadata".into(), + TypeSignature::OptionalType(Box::new(TypeSignature::SequenceType( + SequenceSubtype::StringType(StringSubtype::UTF8( + StringUTF8Length::try_from(64usize).unwrap(), + )), + ))), + ), + ]) + .unwrap(), + ); + + let value = ClarityValue::deserialize(&result, &typesig); + ContractVersion::try_from(value).map_err(Error::from) } /// Check that the version of `subnet.clar` the node is configured to use is supported - fn check_l1_contract_version(&self) -> Result<(), Error> { - const EXACT_MAJOR_VERSION: u32 = 2; - const MINIMUM_MINOR_VERSION: u32 = 0; - const MINIMUM_PATCH_VERSION: u32 = 0; + fn get_validated_l1_contract_version(&self) -> Result { + const EXACT_MAJOR_VERSION: u128 = 2; + const MINIMUM_MINOR_VERSION: u128 = 0; + const MINIMUM_PATCH_VERSION: u128 = 0; + let version = self.get_l1_contract_version()?; let ContractVersion { major, minor, patch, .. - } = self.get_l1_contract_version()?; + } = version; if major != EXACT_MAJOR_VERSION { let msg = format!("Major version must be {EXACT_MAJOR_VERSION} (found {major})"); - return Err(Error::UnsupportedBurnchainContract(msg)); + return Err(Error::BurnchainContractVersion(msg)); }; if minor < MINIMUM_MINOR_VERSION { let msg = format!("Minor version must be at least {MINIMUM_MINOR_VERSION} (found {minor})"); - return Err(Error::UnsupportedBurnchainContract(msg)); + return Err(Error::BurnchainContractVersion(msg)); }; if minor == MINIMUM_MINOR_VERSION && patch < MINIMUM_PATCH_VERSION { let msg = format!("Patch version must be at least {MINIMUM_PATCH_VERSION} (found {patch})"); - return Err(Error::UnsupportedBurnchainContract(msg)); + return Err(Error::BurnchainContractVersion(msg)); }; - Ok(()) + Ok(version) } /// Check that the version of `subnet.clar` the node is configured to use is supported fn l1_contract_ok(&mut self) -> Result<(), Error> { match self.l1_contract_check_passed { true => Ok(()), - false => match self.check_l1_contract_version() { + false => match self.get_validated_l1_contract_version() { // This error is fatal. We can't continue with wrong contract version - Err(Error::UnsupportedBurnchainContract(e)) => { - panic!("Unsupported burnchain contract version: {e}") - } - // Error, but not fatal - Err(e) => Err(e), - Ok(_) => { + Err(e @ Error::BurnchainContractVersion(_)) => panic!("{e}"), + // Error checking version, not fatal + Err(e @ Error::BurnchainContractCheck(_)) => Err(e), + // Error, transform into `Error::BurnchainContractCheck` + Err(e) => Err(Error::BurnchainContractCheck(e.to_string())), + Ok(version) => { + info!("Found supported L1 contract version: {version}"); self.l1_contract_check_passed = true; Ok(()) } diff --git a/testnet/stacks-node/src/burnchains/mod.rs b/testnet/stacks-node/src/burnchains/mod.rs index 799ea1a20..01a9623be 100644 --- a/testnet/stacks-node/src/burnchains/mod.rs +++ b/testnet/stacks-node/src/burnchains/mod.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use std::time::Instant; use self::commitment::Error as CommitmentError; +use clarity::vm::errors::Error as ClarityError; use reqwest::Error as ReqwestError; use stacks::burnchains; use stacks::burnchains::indexer::BurnchainChannel; @@ -41,8 +42,11 @@ mod tests; #[derive(Debug)] pub enum Error { UnsupportedBurnchain(String), - /// Problem with contract deployed on burnchain - UnsupportedBurnchainContract(String), + /// Was not able to check burnchain contract version + BurnchainContractCheck(String), + /// Was able to check burnchain contract version and is unsupported + BurnchainContractVersion(String), + Clarity(ClarityError), CoordinatorClosed, IndexerError(burnchains::Error), RPCError(String), @@ -55,9 +59,13 @@ impl fmt::Display for Error { Error::UnsupportedBurnchain(ref chain_name) => { write!(f, "Burnchain is not supported: {chain_name:?}") } - Error::UnsupportedBurnchainContract(ref msg) => { - write!(f, "Burnchain contract is not supported: {msg}") + Error::BurnchainContractCheck(ref e) => { + write!(f, "Burnchain contract check failed: {e}") } + Error::BurnchainContractVersion(ref e) => { + write!(f, "Burnchain contract unsupported version: {e}") + } + Error::Clarity(ref e) => write!(f, "Clarity Error: {e}"), Error::CoordinatorClosed => write!(f, "ChainsCoordinator closed"), Error::IndexerError(ref e) => write!(f, "Indexer error: {e:?}"), Error::RPCError(ref e) => write!(f, "ControllerError(RPCError: {e})"), @@ -66,6 +74,12 @@ impl fmt::Display for Error { } } +impl From for Error { + fn from(e: ClarityError) -> Self { + Error::Clarity(e) + } +} + impl From for Error { fn from(e: ReqwestError) -> Self { Error::RPCError(e.to_string())