Skip to content

Commit c4a5d72

Browse files
authored
Merge pull request #133 from kinode-dao/develop
develop 0.10.2
2 parents d6a7323 + c0dfeee commit c4a5d72

File tree

11 files changed

+145
-62
lines changed

11 files changed

+145
-62
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "kinode_process_lib"
33
authors = ["Sybil Technologies AG"]
4-
version = "0.10.1"
4+
version = "0.10.2"
55
edition = "2021"
66
description = "A library for writing Kinode processes in Rust."
77
homepage = "https://kinode.org"
@@ -27,6 +27,7 @@ mime_guess = "2.0"
2727
serde = { version = "1.0", features = ["derive"] }
2828
serde_json = "1.0.120"
2929
rand = "0.8"
30+
regex = "1.11.1"
3031
rmp-serde = "1.1.2"
3132
thiserror = "1.0"
3233
tracing = { version = "0.1", optional = true }

src/eth.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub use alloy::rpc::types::{
1010
pub use alloy_primitives::{Address, BlockHash, BlockNumber, Bytes, TxHash, U128, U256, U64, U8};
1111
use serde::{Deserialize, Serialize};
1212
use std::collections::{HashMap, HashSet};
13+
use std::error::Error;
14+
use std::fmt;
1315

1416
/// Subscription kind. Pulled directly from alloy (https://github.com/alloy-rs/alloy).
1517
/// Why? Because alloy is not yet 1.0 and the types in this interface must be stable.
@@ -130,6 +132,24 @@ pub enum EthError {
130132
RpcMalformedResponse,
131133
}
132134

135+
impl fmt::Display for EthError {
136+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137+
match self {
138+
EthError::RpcError(e) => write!(f, "RPC error: {:?}", e),
139+
EthError::MalformedRequest => write!(f, "Malformed request"),
140+
EthError::NoRpcForChain => write!(f, "No RPC provider for chain"),
141+
EthError::SubscriptionClosed(id) => write!(f, "Subscription {} closed", id),
142+
EthError::InvalidMethod(m) => write!(f, "Invalid method: {}", m),
143+
EthError::InvalidParams => write!(f, "Invalid parameters"),
144+
EthError::PermissionDenied => write!(f, "Permission denied"),
145+
EthError::RpcTimeout => write!(f, "RPC request timed out"),
146+
EthError::RpcMalformedResponse => write!(f, "RPC returned malformed response"),
147+
}
148+
}
149+
}
150+
151+
impl Error for EthError {}
152+
133153
/// The action type used for configuring eth:distro:sys. Only processes which have the "root"
134154
/// [`crate::Capability`] from eth:distro:sys can successfully send this action.
135155
#[derive(Clone, Debug, Serialize, Deserialize)]

src/http/server.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::vfs::{FileType, VfsAction, VfsRequest, VfsResponse};
22
use crate::{
3-
get_blob, last_blob, Address, LazyLoadBlob as KiBlob, Message, Request as KiRequest,
3+
get_blob, last_blob, LazyLoadBlob as KiBlob, Message, Request as KiRequest,
44
Response as KiResponse,
55
};
66
pub use http::StatusCode;
@@ -809,11 +809,11 @@ impl HttpServer {
809809
/// An error will be returned if the file does not exist.
810810
pub fn serve_file(
811811
&mut self,
812-
our: &Address,
813812
file_path: &str,
814813
paths: Vec<&str>,
815814
config: HttpBindingConfig,
816815
) -> Result<(), HttpServerError> {
816+
let our = crate::our();
817817
let _res = KiRequest::to(("our", "vfs", "distro", "sys"))
818818
.body(
819819
serde_json::to_vec(&VfsRequest {
@@ -887,11 +887,11 @@ impl HttpServer {
887887
/// An error will be returned if the file does not exist.
888888
pub fn serve_ui(
889889
&mut self,
890-
our: &Address,
891890
directory: &str,
892891
roots: Vec<&str>,
893892
config: HttpBindingConfig,
894893
) -> Result<(), HttpServerError> {
894+
let our = crate::our();
895895
let initial_path = format!("{}/pkg/{}", our.package_id(), directory);
896896

897897
let mut queue = std::collections::VecDeque::new();

src/kimap.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use alloy::rpc::types::request::{TransactionInput, TransactionRequest};
55
use alloy::{hex, primitives::keccak256};
66
use alloy_primitives::{Address, Bytes, FixedBytes, B256};
77
use alloy_sol_types::{SolCall, SolEvent, SolValue};
8+
use contract::tokenCall;
89
use serde::{Deserialize, Serialize};
10+
use std::error::Error;
11+
use std::fmt;
912
use std::str::FromStr;
1013

1114
/// kimap deployment address on optimism
@@ -210,12 +213,19 @@ pub mod contract {
210213

211214
function supportsInterface(bytes4 interfaceId) external view returns (bool);
212215

213-
/// Retrieves the address of the ERC-6551 implementation of the
214-
/// zeroth entry. This is set once at initialization.
216+
/// Gets the token identifier that owns this token-bound account (TBA).
217+
/// This is a core function of the ERC-6551 standard that returns the
218+
/// identifying information about the NFT that owns this account.
219+
/// The return values are constant and cannot change over time.
215220
///
216221
/// Returns:
217-
/// - implementation: The address of the ERC-6551 implementation.
218-
function get6551Implementation() external view returns (address);
222+
/// - chainId: The EIP-155 chain ID where the owning NFT exists
223+
/// - tokenContract: The contract address of the owning NFT
224+
/// - tokenId: The token ID of the owning NFT
225+
function token()
226+
external
227+
view
228+
returns (uint256 chainId, address tokenContract, uint256 tokenId);
219229
}
220230
}
221231

@@ -259,6 +269,21 @@ pub enum DecodeLogError {
259269
UnresolvedParent(String),
260270
}
261271

272+
impl fmt::Display for DecodeLogError {
273+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274+
match self {
275+
DecodeLogError::UnexpectedTopic(topic) => write!(f, "Unexpected topic: {:?}", topic),
276+
DecodeLogError::InvalidName(name) => write!(f, "Invalid name: {}", name),
277+
DecodeLogError::DecodeError(err) => write!(f, "Decode error: {}", err),
278+
DecodeLogError::UnresolvedParent(parent) => {
279+
write!(f, "Could not resolve parent: {}", parent)
280+
}
281+
}
282+
}
283+
}
284+
285+
impl Error for DecodeLogError {}
286+
262287
/// Canonical function to determine if a kimap entry is valid. This should
263288
/// be used whenever reading a new kimap entry from a mints query, because
264289
/// while most frontends will enforce these rules, it is possible to post
@@ -514,6 +539,28 @@ impl Kimap {
514539
Ok((res.tba, res.owner, note_data))
515540
}
516541

542+
/// Gets a namehash from an existing TBA address.
543+
///
544+
/// # Parameters
545+
/// - `tba`: The TBA to get the namehash of.
546+
/// # Returns
547+
/// A `Result<String, EthError>` representing the namehash of the TBA.
548+
pub fn get_namehash_from_tba(&self, tba: Address) -> Result<String, EthError> {
549+
let token_call = tokenCall {}.abi_encode();
550+
551+
let tx_req = TransactionRequest::default()
552+
.input(TransactionInput::new(token_call.into()))
553+
.to(tba);
554+
555+
let res_bytes = self.provider.call(tx_req, None)?;
556+
557+
let res = tokenCall::abi_decode_returns(&res_bytes, false)
558+
.map_err(|_| EthError::RpcMalformedResponse)?;
559+
560+
let namehash: FixedBytes<32> = res.tokenId.into();
561+
Ok(format!("0x{}", hex::encode(namehash)))
562+
}
563+
517564
/// Create a filter for all mint events.
518565
pub fn mint_filter(&self) -> crate::eth::Filter {
519566
crate::eth::Filter::new()

src/logging.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
pub use tracing::{debug, error, info, warn, Level};
2-
use tracing_error::ErrorLayer;
3-
use tracing_subscriber::{
4-
fmt, layer::SubscriberExt, prelude::*, util::SubscriberInitExt, EnvFilter,
5-
};
6-
71
use crate::{
82
print_to_terminal,
93
vfs::{create_drive, open_file, File},
104
Address, Request,
115
};
6+
pub use tracing::{debug, error, info, warn, Level};
7+
use tracing_error::ErrorLayer;
8+
use tracing_subscriber::{
9+
fmt, layer::SubscriberExt, prelude::*, util::SubscriberInitExt, EnvFilter,
10+
};
1211

1312
pub struct RemoteLogSettings {
1413
pub target: Address,
@@ -25,10 +24,12 @@ pub struct RemoteWriterMaker {
2524

2625
pub struct FileWriter {
2726
pub file: File,
27+
pub max_size: u64,
2828
}
2929

3030
pub struct FileWriterMaker {
3131
pub file: File,
32+
pub max_size: u64,
3233
}
3334

3435
pub struct TerminalWriter {
@@ -65,6 +66,28 @@ impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for RemoteWriterMaker {
6566
impl std::io::Write for FileWriter {
6667
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
6768
// TODO: use non-blocking call instead? (.append() `send_and_await()`s)
69+
let metadata = self
70+
.file
71+
.metadata()
72+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
73+
74+
if metadata.len > self.max_size {
75+
// Get current contents
76+
let contents = self
77+
.file
78+
.read()
79+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
80+
81+
// Take second half of file
82+
let half_len = contents.len() / 2;
83+
let new_contents = &contents[half_len..];
84+
85+
// Truncate and write back second half
86+
self.file
87+
.write(new_contents)
88+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
89+
}
90+
6891
self.file
6992
.append(buf)
7093
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
@@ -82,6 +105,7 @@ impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for FileWriterMaker {
82105
fn make_writer(&'a self) -> Self::Writer {
83106
FileWriter {
84107
file: File::new(self.file.path.clone(), self.file.timeout),
108+
max_size: self.max_size,
85109
}
86110
}
87111
}
@@ -114,17 +138,21 @@ impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for TerminalWriterMaker {
114138
/// Logs will be printed to terminal as appropriate depending on given level.
115139
/// Logs will be logged into the logging file as appropriate depending on the given level.
116140
///
141+
/// If `max_log_file_size` is provided, the log file will be rotated when it reaches
142+
/// the given size. The default size is 1MB.
143+
///
117144
/// The logging file lives in the node's `vfs/` directory, specifically at
118145
/// `node/vfs/package:publisher.os/log/process.log`, where `node` is your node's home
119146
/// directory, `package` is the package name, `publisher.os` is the publisher of the
120147
/// package, and `process` is the process name of the process doing the logging.
121148
pub fn init_logging(
122-
our: &Address,
123149
file_level: Level,
124150
terminal_level: Level,
125151
remote: Option<RemoteLogSettings>,
126152
terminal_levels_mapping: Option<(u8, u8, u8, u8)>,
153+
max_log_file_size: Option<u64>,
127154
) -> anyhow::Result<()> {
155+
let our = crate::our();
128156
let log_dir_path = create_drive(our.package_id(), "log", None)?;
129157
let log_file_path = format!("{log_dir_path}/{}.log", our.process());
130158
let log_file = open_file(&log_file_path, true, None)?;
@@ -142,7 +170,10 @@ pub fn init_logging(
142170
let debug_filter = tracing_subscriber::filter::filter_fn(|metadata: &tracing::Metadata<'_>| {
143171
metadata.level() == &Level::DEBUG
144172
});
145-
let file_writer_maker = FileWriterMaker { file: log_file };
173+
let file_writer_maker = FileWriterMaker {
174+
file: log_file,
175+
max_size: max_log_file_size.unwrap_or(1024 * 1024),
176+
};
146177
let (error, warn, info, debug) = terminal_levels_mapping.unwrap_or_else(|| (0, 1, 2, 3));
147178
let error_terminal_writer_maker = TerminalWriterMaker { level: error };
148179
let warn_terminal_writer_maker = TerminalWriterMaker { level: warn };

src/net.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub enum IndexerRequests {
128128
#[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
129129
pub struct NamehashToNameRequest {
130130
pub hash: String,
131-
pub block: Option<u64>,
131+
pub block: u64,
132132
}
133133

134134
/// Response from `kns-indexer:kns-indexer:sys`.
@@ -226,7 +226,7 @@ where
226226
.body(
227227
serde_json::to_vec(&IndexerRequests::NamehashToName(NamehashToNameRequest {
228228
hash: namehash.into(),
229-
block,
229+
block: block.unwrap_or(0),
230230
}))
231231
.unwrap(),
232232
)

src/types/message.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ impl Message {
8383
}
8484
/// Check if a `Message` was sent by a local process.
8585
/// Returns `false` if the `source` node is not our local node.
86-
pub fn is_local(&self, our: &Address) -> bool {
86+
pub fn is_local(&self) -> bool {
87+
let our = crate::our();
8788
match self {
8889
Message::Request { source, .. } => source.node == our.node,
8990
Message::Response { source, .. } => source.node == our.node,

src/types/package_id.rs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -129,28 +129,18 @@ impl<'de> Deserialize<'de> for PackageId {
129129

130130
impl std::str::FromStr for PackageId {
131131
type Err = ProcessIdParseError;
132-
/// Attempt to parse a `PackageId` from a string. The string must
133-
/// contain exactly two segments, where segments are non-empty strings
134-
/// separated by a colon (`:`). The segments cannot themselves contain colons.
132+
/// Attempts to parse a `PackageId` from a string. The string must match the pattern
133+
/// of two segments containing only lowercase letters, numbers and hyphens, separated by a colon.
135134
fn from_str(input: &str) -> Result<Self, Self::Err> {
136-
let segments: Vec<&str> = input.split(':').collect();
137-
if segments.len() < 2 {
138-
return Err(ProcessIdParseError::MissingField);
139-
} else if segments.len() > 2 {
140-
return Err(ProcessIdParseError::TooManyColons);
141-
}
142-
let package_name = segments[0].to_string();
143-
if package_name.is_empty() {
144-
return Err(ProcessIdParseError::MissingField);
145-
}
146-
let publisher_node = segments[1].to_string();
147-
if publisher_node.is_empty() {
148-
return Err(ProcessIdParseError::MissingField);
135+
let re = regex::Regex::new(r"^[a-z0-9-]+:[a-z0-9-]+$").unwrap();
136+
if !re.is_match(input) {
137+
return Err(ProcessIdParseError::InvalidCharacter);
149138
}
150139

140+
let segments: Vec<&str> = input.split(':').collect();
151141
Ok(PackageId {
152-
package_name,
153-
publisher_node,
142+
package_name: segments[0].to_string(),
143+
publisher_node: segments[1].to_string(),
154144
})
155145
}
156146
}

0 commit comments

Comments
 (0)