Архивация в Arweave

This commit is contained in:
AidarKC
2026-09-12 10:17:23 +03:00
parent ef068eca81
commit c3253dceac
48 changed files with 6681 additions and 1227 deletions
@@ -36,6 +36,7 @@ const BLOCK_TYPE_SERVER_PROFILE: u8 = 30;
const BLOCK_TYPE_ACCESS_SERVERS: u8 = 40;
const BLOCK_TYPE_SESSIONS: u8 = 50;
const BLOCK_TYPE_TRUSTED_STATE: u8 = 70;
const BLOCK_TYPE_ARCHIVE_HEAD: u8 = 100;
const BLOCK_VERSION_0: u8 = 0;
const BLOCKCHAIN_TYPE_MAIN_USER: u8 = 1;
const SESSIONS_MODE_MIXED: u8 = 1;
@@ -163,6 +164,8 @@ pub struct UpdateUserPdaArgs {
pub prev_hash: [u8; 32],
pub additional_limit: u64,
pub fields: UserMutableFields,
/// None = legacy instruction, archive head сохранить; Some(None) = очистить; Some(Some) = заменить.
pub archive_head_update: Option<Option<ArchiveHeadRecord>>,
pub signature: [u8; 64],
}
@@ -210,6 +213,12 @@ pub struct BlockchainRecord {
pub arweave_tx_id: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArchiveHeadRecord {
pub arweave_tx_id: [u8; 32],
pub archive_hash: [u8; 32],
}
#[derive(Clone, Debug)]
pub struct UserRecord {
pub created_at_ms: u64,
@@ -230,6 +239,7 @@ pub struct UserRecord {
pub sessions_mode: u8,
pub sessions: Vec<SessionRecord>,
pub trusted_count: u8,
pub archive_head: Option<ArchiveHeadRecord>,
pub signature: [u8; 64],
}
@@ -349,17 +359,34 @@ fn parse_create_args(r: &mut Reader<'_>) -> Result<CreateUserPdaArgs, ProgramErr
}
fn parse_update_args(r: &mut Reader<'_>) -> Result<UpdateUserPdaArgs, ProgramError> {
let login = r.read_string_u8()?;
let recovery_key = r.read_pubkey()?;
let root_key = r.read_pubkey()?;
let created_at_ms = r.read_u64()?;
let updated_at_ms = r.read_u64()?;
let version = r.read_u32()?;
let prev_hash = r.read_fixed_32()?;
let additional_limit = r.read_u64()?;
let fields = parse_fields(r)?;
// Backward compatibility: legacy update после trusted_count содержит ровно 64-byte root signature.
let archive_head_update = if r.remaining() > 64 {
let present = r.read_u8()?;
match present {
0 => Some(None),
1 => Some(Some(ArchiveHeadRecord {
arweave_tx_id: r.read_fixed_32()?,
archive_hash: r.read_fixed_32()?,
})),
_ => return Err(ProgramError::from(ShineUsersError::InvalidInstruction)),
}
} else {
None
};
Ok(UpdateUserPdaArgs {
login: r.read_string_u8()?,
recovery_key: r.read_pubkey()?,
root_key: r.read_pubkey()?,
created_at_ms: r.read_u64()?,
updated_at_ms: r.read_u64()?,
version: r.read_u32()?,
prev_hash: r.read_fixed_32()?,
additional_limit: r.read_u64()?,
fields: parse_fields(r)?,
signature: r.read_fixed_64()?,
login, recovery_key, root_key, created_at_ms, updated_at_ms, version, prev_hash,
additional_limit, fields, archive_head_update, signature: r.read_fixed_64()?,
})
}
@@ -590,6 +617,7 @@ fn process_create_user_pda<'a>(program_id: &Pubkey, accounts: &'a [AccountInfo<'
sessions_mode: args.fields.sessions_mode,
sessions: args.fields.sessions,
trusted_count: args.fields.trusted_count,
archive_head: None,
signature: [0; 64],
};
validate_blockchain_limits(&record.blockchain, 0, 0, true)?;
@@ -713,6 +741,10 @@ fn build_update_record(old_record: &UserRecord, args: &UpdateUserPdaArgs, new_ba
sessions_mode: args.fields.sessions_mode,
sessions: args.fields.sessions.clone(),
trusted_count: args.fields.trusted_count,
archive_head: match &args.archive_head_update {
None => old_record.archive_head.clone(),
Some(value) => value.clone(),
},
signature: [0; 64],
})
}
@@ -857,6 +889,7 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
let mut sessions_mode = SESSIONS_MODE_MIXED;
let mut sessions = Vec::new();
let mut trusted_count = 0u8;
let mut archive_head: Option<ArchiveHeadRecord> = None;
for _ in 0..blocks_count {
let block_type = read_u8_from(useful, &mut cursor)?;
@@ -907,6 +940,13 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
BLOCK_TYPE_TRUSTED_STATE => {
trusted_count = read_u8_from(useful, &mut cursor)?;
}
BLOCK_TYPE_ARCHIVE_HEAD => {
require!(archive_head.is_none(), ShineUsersError::InvalidRecordData);
archive_head = Some(ArchiveHeadRecord {
arweave_tx_id: read_fixed_32_from(useful, &mut cursor)?,
archive_hash: read_fixed_32_from(useful, &mut cursor)?,
});
}
_ => return Err(ProgramError::from(ShineUsersError::InvalidRecordFormat)),
}
}
@@ -933,6 +973,7 @@ fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
sessions_mode,
sessions,
trusted_count,
archive_head,
signature,
})
}
@@ -986,7 +1027,7 @@ fn serialize_unsigned_record(record: &UserRecord) -> Result<Vec<u8>, ProgramErro
out.push(login_bytes.len() as u8);
out.extend_from_slice(login_bytes);
let blocks_count = if record.is_server { 8 } else { 7 };
let blocks_count = (if record.is_server { 8 } else { 7 }) + if record.archive_head.is_some() { 1 } else { 0 };
out.push(blocks_count);
write_recovery_key_block(&mut out, record);
write_root_key_block(&mut out, record);
@@ -996,6 +1037,7 @@ fn serialize_unsigned_record(record: &UserRecord) -> Result<Vec<u8>, ProgramErro
write_access_servers_block(&mut out, record)?;
write_sessions_block(&mut out, record)?;
write_trusted_state_block(&mut out, record);
if let Some(archive_head) = &record.archive_head { write_archive_head_block(&mut out, archive_head); }
let record_len = out.len().checked_add(64).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?;
require!(record_len <= u16::MAX as usize, ShineUsersError::RecordTooLarge);
@@ -1045,6 +1087,7 @@ fn write_sessions_block(out: &mut Vec<u8>, record: &UserRecord) -> Result<(), Pr
out.push(BLOCK_TYPE_SESSIONS); out.push(BLOCK_VERSION_0); out.push(record.sessions_mode); require!(record.sessions.len() <= MAX_SESSIONS, ShineUsersError::InvalidRecordData); out.push(record.sessions.len() as u8); for session in &record.sessions { out.push(session.session_type); out.push(session.session_version); write_len_prefixed_string(out, &session.session_name)?; out.extend_from_slice(session.session_pub_key.as_ref()); } Ok(())
}
fn write_trusted_state_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_TRUSTED_STATE); out.push(BLOCK_VERSION_0); out.push(record.trusted_count); }
fn write_archive_head_block(out: &mut Vec<u8>, archive: &ArchiveHeadRecord) { out.push(BLOCK_TYPE_ARCHIVE_HEAD); out.push(BLOCK_VERSION_0); out.extend_from_slice(&archive.arweave_tx_id); out.extend_from_slice(&archive.archive_hash); }
fn write_len_prefixed_string(out: &mut Vec<u8>, value: &str) -> Result<(), ProgramError> { let bytes = value.as_bytes(); require!(bytes.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); out.push(bytes.len() as u8); out.extend_from_slice(bytes); Ok(()) }
fn verify_record_signature_hash(instructions_sysvar: &AccountInfo, root_key: &Pubkey, signature: &[u8; 64], message_hash: &[u8]) -> Result<[u8; 64], ProgramError> {