diff --git a/.env.example b/.env.example index 81fa558e474..8d614a59406 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,28 @@ # BlockStore Adapter Configuration -BLOCKSTORE_STORE=false +BLOCKSTORE_STORE_CHARACTER=false +BLOCKSTORE_STORE_MEMORY=false BLOCKSTORE_CHAIN=base # ethereum/base/celestia -BLOCKSTORE_RECOVERY=false +BLOCKSTORE_RECOVERY_ALL=false BLOCKSTORE_REGISTRY_URL= # e.g. "http://localhost:8545" BLOCKSTORE_REGISTRY_ADDR= # e.g. "0x{contract-address}" BLOCKSTORE_REGISTRY_PRIVATEKEY= # e.g. "0x{preivate-key-hex}" BLOCKSTORE_DATA_URL= # e.g. "http://localhost:8545" BLOCKSTORE_DATA_PRIVATEKEY= # e.g. "0x{preivate-key-hex}" +# BlockStore Adapter Configuration +BLOCKSTORE_STORE_CHARACTER=false +BLOCKSTORE_STORE_MEMORY=false +BLOCKSTORE_CHAIN=evm # ethereum/evm/celestia +BLOCKSTORE_RECOVERY_ALL=false +BLOCKSTORE_RECOVERY_BLOB_COUNT= # {number}, default to all + +BLOCKSTORE_REGISTRY_URL= # e.g. "http://localhost:8545" +BLOCKSTORE_REGISTRY_ADDR= # e.g. "0x{contract-address}" +BLOCKSTORE_REGISTRY_PRIVATEKEY= # e.g. "0x{preivate-key-hex}" + +BLOCKSTORE_DATA_URL= # e.g. "http://localhost:8545" +BLOCKSTORE_DATA_PRIVATEKEY= # e.g. "0x{preivate-key-hex}" + # Discord Configuration DISCORD_APPLICATION_ID= DISCORD_API_TOKEN= # Bot token diff --git a/agent/src/index.ts b/agent/src/index.ts index f5b61798ffc..df05f2c12eb 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -430,10 +430,10 @@ async function startAgent(character: Character, directClient) { character.username ??= character.name; const blockStoreAdapter = new BlockStoreQueue(character.id); - if (process.env.BLOCKSTORE_RECOVERY == "true") { + if (process.env.BLOCKSTORE_RECOVERY_ALL.toLowerCase() == "true") { const bsUtil = new BlockStoreUtil(character.id); - character = await bsUtil.restoreCharacter(character); - } else if (process.env.BLOCKSTORE_STORE == "true") { + character = await bsUtil.restoreCharacter(); + } else if (process.env.BLOCKSTORE_STORE_CHARACTER.toLowerCase() == "true") { blockStoreAdapter.enqueue(BlockStoreMsgType.character, character); } @@ -449,7 +449,7 @@ async function startAgent(character: Character, directClient) { await db.init(); - if (process.env.BLOCKSTORE_RECOVERY == "true") { + if (process.env.BLOCKSTORE_RECOVERY_ALL.toLowerCase() == "true") { const bsUtil = new BlockStoreUtil(character.id, db); await bsUtil.restoreMemory(); } diff --git a/packages/adapter-blockchain/src/blockchain.ts b/packages/adapter-blockchain/src/blockchain.ts index e07438c1ec2..6d003119145 100644 --- a/packages/adapter-blockchain/src/blockchain.ts +++ b/packages/adapter-blockchain/src/blockchain.ts @@ -6,7 +6,7 @@ import { import { IBlockchain, Message } from "./types"; import Web3 from 'web3'; - export class BaseStoreAdapter implements IBlockchain { + export class EvmCompatibleStoreAdapter implements IBlockchain { private account; private web3; @@ -127,8 +127,8 @@ export function createBlockchain( return new EthereumAdapter(); case "celestia": return new CelestiaStoreAdapter(); - case "base": - return new BaseStoreAdapter(); + case "evm": + return new EvmCompatibleStoreAdapter(); default: throw new Error(`Unknown blockchain adapter: ${chain}`); } diff --git a/packages/adapter-blockchain/src/contract.ts b/packages/adapter-blockchain/src/contract.ts deleted file mode 100644 index 1a6f29f2673..00000000000 --- a/packages/adapter-blockchain/src/contract.ts +++ /dev/null @@ -1,171 +0,0 @@ -export const RegistryABI = [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "string", - "name": "key", - "type": "string" - } - ], - "name": "Deleted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "string", - "name": "key", - "type": "string" - }, - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "string", - "name": "key", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "value", - "type": "string" - }, - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "Registered", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "key", - "type": "string" - } - ], - "name": "deleteKey", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "key", - "type": "string" - } - ], - "name": "getKeyOwner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "key", - "type": "string" - } - ], - "name": "getValue", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "key", - "type": "string" - } - ], - "name": "keyExists", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "key", - "type": "string" - }, - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "name": "registerOrUpdate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "key", - "type": "string" - }, - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -]; \ No newline at end of file diff --git a/packages/adapter-blockchain/src/contract/AgentRegistry.sol b/packages/adapter-blockchain/src/contract/AgentRegistry.sol index dbd951c102d..113cd262413 100644 --- a/packages/adapter-blockchain/src/contract/AgentRegistry.sol +++ b/packages/adapter-blockchain/src/contract/AgentRegistry.sol @@ -2,65 +2,96 @@ pragma solidity ^0.8.0; contract AgentRegistry { - // Mapping to store the UUID (key) and corresponding custom value (value) - mapping(string => string) private registry; + // Mappings for blobIdx, keyStore, and character. + mapping(string => string) private blobIdxMapping; + mapping(string => string) private keyStoreMapping; + mapping(string => string) private characterMapping; - // Mapping to store the owner of each key + // Mapping to store the owner of each agentID mapping(string => address) private keyOwners; - // Event to log registration, update, or deletion - event Registered(string indexed key, string value, address indexed owner); - event Deleted(string indexed key); - event OwnershipTransferred(string indexed key, address indexed previousOwner, address indexed newOwner); + // Events for logging updates and ownership changes + event BlobIdxUpdated(string indexed agentID, string blobIdx); + event KeyStoreUpdated(string indexed agentID, string keyStore); + event CharacterUpdated(string indexed agentID, string character); + event AgentDeleted(string indexed agentID); + event OwnershipTransferred(string indexed agentID, address indexed previousOwner, address indexed newOwner); - // Modifier to ensure the user can only modify their own key - modifier onlyKeyOwner(string memory key) { - require(keyOwners[key] == msg.sender, "Not the owner of this key"); + // Modifier to ensure the caller is the owner of the agentID + modifier onlyAgentOwner(string memory agentID) { + require(keyOwners[agentID] == msg.sender, "Not the owner of this agentID"); _; } - // Register or update a key with its associated custom value - function registerOrUpdate(string memory key, string memory value) public { - // If the key is new, assign ownership to the sender - if (keyOwners[key] == address(0)) { - keyOwners[key] = msg.sender; + // Update or register ownership of the agentID (if not already assigned) + function updateOrRegisterOwnership(string memory agentID) internal { + if (keyOwners[agentID] == address(0)) { + keyOwners[agentID] = msg.sender; + } else { + require(keyOwners[agentID] == msg.sender, "Not the owner of this agentID"); } + } + + // Update or register blobIdx for a given agent + function updateOrRegisterBlobIdx(string memory agentID, string memory newBlobIdx) external { + updateOrRegisterOwnership(agentID); // Ensure ownership + blobIdxMapping[agentID] = newBlobIdx; + emit BlobIdxUpdated(agentID, newBlobIdx); + } - // Ensure the sender is the owner of the key - require(keyOwners[key] == msg.sender, "Not the owner of this key"); + // Update or register keyStore for a given agent + function updateOrRegisterKeyStore(string memory agentID, string memory newKeyStore) external { + updateOrRegisterOwnership(agentID); // Ensure ownership + keyStoreMapping[agentID] = newKeyStore; + emit KeyStoreUpdated(agentID, newKeyStore); + } - registry[key] = value; - emit Registered(key, value, msg.sender); + // Update or register character for a given agent + function updateOrRegisterCharacter(string memory agentID, string memory newCharacter) external { + updateOrRegisterOwnership(agentID); // Ensure ownership + characterMapping[agentID] = newCharacter; + emit CharacterUpdated(agentID, newCharacter); } - // Delete a key from the registry - function deleteKey(string memory key) public onlyKeyOwner(key) { - require(bytes(registry[key]).length != 0, "Key does not exist"); - delete registry[key]; - delete keyOwners[key]; - emit Deleted(key); + // Delete an agent with a given agent + function deleteAgent(string memory agentID) external onlyAgentOwner(agentID) { + delete blobIdxMapping[agentID]; + delete keyStoreMapping[agentID]; + delete characterMapping[agentID]; + delete keyOwners[agentID]; + emit AgentDeleted(agentID); } - // Transfer ownership of a key to another address - function transferOwnership(string memory key, address newOwner) public onlyKeyOwner(key) { + // Transfer ownership of an agentID to a new owner + function transferOwnership(string memory agentID, address newOwner) external onlyAgentOwner(agentID) { require(newOwner != address(0), "New owner cannot be the zero address"); - address previousOwner = keyOwners[key]; - keyOwners[key] = newOwner; - emit OwnershipTransferred(key, previousOwner, newOwner); + address previousOwner = keyOwners[agentID]; + keyOwners[agentID] = newOwner; + emit OwnershipTransferred(agentID, previousOwner, newOwner); + } + + // Retrieve blobIdx for a given agentID + function getBlobIdx(string memory agentID) external view returns (string memory) { + return blobIdxMapping[agentID]; + } + + // Retrieve keyStore for a given agentID + function getKeyStore(string memory agentID) external view returns (string memory) { + return keyStoreMapping[agentID]; } - // Retrieve the value associated with a given key - function getValue(string memory key) public view returns (string memory) { - return registry[key]; + // Retrieve character for a given agentID + function getCharacter(string memory agentID) external view returns (string memory) { + return characterMapping[agentID]; } - // Check if a key exists in the registry - function keyExists(string memory key) public view returns (bool) { - return bytes(registry[key]).length != 0; + // Check if an agentID exists + function agentExists(string memory agentID) external view returns (bool) { + return keyOwners[agentID] != address(0); } - // Retrieve the owner of a given key - function getKeyOwner(string memory key) public view returns (address) { - return keyOwners[key]; + // Retrieve the owner of a given agentID + function getAgentOwner(string memory agentID) external view returns (address) { + return keyOwners[agentID]; } } diff --git a/packages/adapter-blockchain/src/contract/abi.ts b/packages/adapter-blockchain/src/contract/abi.ts new file mode 100644 index 00000000000..69a56055fc1 --- /dev/null +++ b/packages/adapter-blockchain/src/contract/abi.ts @@ -0,0 +1,290 @@ +export const RegistryABI = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "AgentDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "blobIdx", + "type": "string" + } + ], + "name": "BlobIdxUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "character", + "type": "string" + } + ], + "name": "CharacterUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "deleteAgent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "keyStore", + "type": "string" + } + ], + "name": "KeyStoreUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "internalType": "string", + "name": "newBlobIdx", + "type": "string" + } + ], + "name": "updateOrRegisterBlobIdx", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "internalType": "string", + "name": "newCharacter", + "type": "string" + } + ], + "name": "updateOrRegisterCharacter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + }, + { + "internalType": "string", + "name": "newKeyStore", + "type": "string" + } + ], + "name": "updateOrRegisterKeyStore", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "updateOrRegisterOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "agentExists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "getAgentOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "getBlobIdx", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "getCharacter", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "agentID", + "type": "string" + } + ], + "name": "getKeyStore", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +]; \ No newline at end of file diff --git a/packages/adapter-blockchain/src/queue.ts b/packages/adapter-blockchain/src/queue.ts index 43672c400ee..2d9b7a9ae5b 100644 --- a/packages/adapter-blockchain/src/queue.ts +++ b/packages/adapter-blockchain/src/queue.ts @@ -89,11 +89,24 @@ export class BlockStoreQueue implements IBlockStoreAdapter { } private async processBatchTask(tasks: { msgType: BlockStoreMsgType; msg: any }[]): Promise { + const characters = tasks + .filter(task => task.msgType == BlockStoreMsgType.character) + .map(task => task.msg); + + if (characters.length > 0) { + // only update to latest character + const ret = await this.processCharacter(characters[characters.length-1]); + if (!ret) { + elizaLogger.error("Process character update failed"); + } + } + + const blobTasks = tasks.filter(task => task.msgType !== BlockStoreMsgType.character); // get last idx - const idx = await this.registry.getValue(this.id); + const idx = await this.registry.getBlobIdx(this.id); // marshal the messages - const blob = tasks.map(({ msgType, msg }) => ({ + const blob = blobTasks.map(({ msgType, msg }) => ({ msgType, data: JSON.stringify(msg).trim(), })); @@ -106,11 +119,18 @@ export class BlockStoreQueue implements IBlockStoreAdapter { const uIdx = await this.blockChain.push(JSON.stringify(message).trim()); // update idx - const ret = await this.registry.registerOrUpdate(this.id, uIdx); + const ret = await this.registry.updateOrRegisterBlobIdx(this.id, uIdx); if (!ret) { - elizaLogger.error("Update registry failed"); - } else { - elizaLogger.info(`Upload messages with idx ${uIdx} to blockchain`); + elizaLogger.error("Update to blockchain failed"); } } + + private async processCharacter(msg: any): Promise { + // submit the character to blob + const characterData = JSON.stringify(msg).trim(); + const idx = await this.blockChain.push(characterData); + + // save the idx of character to registry + return await this.registry.updateOrRegisterCharacter(this.id, idx); + } } diff --git a/packages/adapter-blockchain/src/registry.ts b/packages/adapter-blockchain/src/registry.ts index 6517fa7ce23..afc9e5c033e 100644 --- a/packages/adapter-blockchain/src/registry.ts +++ b/packages/adapter-blockchain/src/registry.ts @@ -1,5 +1,5 @@ import Web3 from 'web3'; -import { RegistryABI } from './contract'; +import { RegistryABI } from './contract/abi'; import { elizaLogger } from '@ai16z/eliza'; export class Registry { @@ -26,42 +26,100 @@ export class Registry { } /** - * Get the value for a given key from the contract. - * @param id The key to query. - * @returns The associated value of the key. - */ - async getValue(id: string): Promise { - if (id == "") { + * Get the idx for a given agent id from the contract. + * @param id The agent id to query. + * @returns The associated Idx of the id. + */ + async getBlobIdx(id: string): Promise { + if (id === "") { return "" } + const idx = await this.getFromContract("getBlobIdx", [id]); + return idx; + } + + /** + * Register or update idx for a given agent id in the contract. + * @param id The agent id to register or update. + * @param idx The idx to associate with the agent id. + */ + async updateOrRegisterBlobIdx(id: string, idx: string): Promise { + elizaLogger.info(`Updating memories at index ${idx} to blockchain`); + return await this.updateInContract("updateOrRegisterBlobIdx", [id, idx]); + } + + /** + * Get stored character json string for a given agent id from the contract. + * @param id The agent id to query. + * @returns The associated character json string of the id. + */ + async getCharacter(id: string): Promise { + if (id === "") { + return "" + } + + const idx = await this.getFromContract("getCharacter", [id]); + return idx; + } + + /** + * Register or update idx for a given agent id in the contract. + * @param id The agent id to register or update. + * @param character The character json string to associate with the agent id. + */ + async updateOrRegisterCharacter(id: string, character: string): Promise { + elizaLogger.info(`Update new character to blockchain`); + return await this.updateInContract("updateOrRegisterCharacter", [id, character]); + } + + /** + * Get stored keystore json string for a given agent id from the contract. + * @param id The agent id to query. + * @returns The associated Idx of the id. + */ + async getKeyStore(id: string): Promise { + if (id === "") { + return "" + } + + const idx = await this.getFromContract("getKeyStore", [id]); + return idx; + } + + /** + * Register or update idx for a given agent id in the contract. + * @param id The agent id to register or update. + * @param keystore The keystore json string to associate with the agent id. + */ + async updateOrRegisterKeyStore(id: string, keystore: string): Promise { + elizaLogger.info(`Update keystore to blockchain`); + return await this.updateInContract("updateOrRegisterKeyStore", [id, keystore]); + } + + // Get a value from the contract using a specified method. + private async getFromContract(methodName: string, params: any[]): Promise { try { - const idx = await this.contract.methods.getValue(id).call(); - return idx; + const result = await this.contract.methods[methodName](...params).call(); + return result; } catch (error) { - elizaLogger.error("Error during getValue:", error); + elizaLogger.error(`Error during getFromContract (${methodName}):`, error); return ""; } } - /** - * Register or update a key-value pair in the contract. - * @param id The key to register or update. - * @param idx The value to associate with the key. - * @param from The sender's address. - */ - async registerOrUpdate(id: string, idx: string): Promise { - if (id == "") { - elizaLogger.error("Error during registerOrUpdate, id is empty"); - return false - } - - try { - const tx = await this.contract.methods.registerOrUpdate(id, idx).send({ from: this.account.address }); - return true; - } catch (error) { - elizaLogger.error("Error during registerOrUpdate", error); - return false; + // Update a value in the contract using a specified method. + private async updateInContract(methodName: string, params: any[]): Promise { + try { + await this.contract.methods[methodName]( + ...params + ).send( + { from: this.account.address } + ); + return true; + } catch (error) { + elizaLogger.error(`Error during updateInContract (${methodName}):`, error); + return false; + } } - } } \ No newline at end of file diff --git a/packages/adapter-blockchain/src/util.ts b/packages/adapter-blockchain/src/util.ts index 8e74b7272f9..a76abdac615 100644 --- a/packages/adapter-blockchain/src/util.ts +++ b/packages/adapter-blockchain/src/util.ts @@ -15,14 +15,28 @@ export class BlockStoreUtil { private id: string; constructor(id: string, database?: IDatabaseAdapter) { - if (id == "") { - throw new Error("id cannot be empty"); + if (id === "") { + throw new Error("Agent id cannot be empty"); } this.id = id; this.database = database; this.blockChain = createBlockchain(process.env.BLOCKSTORE_CHAIN); } + async restoreCharacter(): Promise { + const idx = (await new Registry().getCharacter(this.id)).trim(); + if (idx === "") { + throw new Error(`Character data for agent id ${this.id} is not valid`); + } + + const blobData = await this.blockChain.pull(idx); + + const character = JSON.parse(blobData.trim()); + elizaLogger.info("Recovering Character from blockchain"); + + return character; + } + async restoreMemory() { if (!this.database) { throw new Error("database is not valid"); @@ -34,16 +48,16 @@ export class BlockStoreUtil { for (let i = headers.length - 2; i >= 0; i--) { const header = headers[i]; const blobData = await this.blockChain.pull(header.prev); - const message: Message = JSON.parse(blobData); + const message: Message = JSON.parse(blobData.trim()); if (!message || !message.blob) { throw new Error("Detected invalid data on the blockchain"); } - elizaLogger.info(`Restore blob ${header.prev} from blockchain`); + elizaLogger.info(`Recovering memories at blob index ${header.prev} from blockchain`); for (const blob of message.blob) { switch (blob.msgType) { case BlockStoreMsgType.memory: { - const memory = JSON.parse(blob.data); + const memory = JSON.parse(blob.data.trim()); if (await this.database.getMemoryById(memory.id) == null) { await this.database.createMemory(memory, "message"); } @@ -59,41 +73,11 @@ export class BlockStoreUtil { } } - async restoreCharacter(character: Character): Promise { - const headers = await this.getAllBlobHeaders(); - - if (headers.length < 2) { - throw new Error("Character idx not found"); - } - - // restore character - const characterHeader = headers[headers.length - 2]; - const blobData = await this.blockChain.pull(characterHeader.prev); - const message: Message = JSON.parse(blobData); - if (!message) { - throw new Error("Detected invalid data on the blockchain"); - } - - if ( - !message || - !message.blob || - message.blob.length === 0 || - message.blob[0].msgType !== BlockStoreMsgType.character - ) { - throw new Error("Character data of blob is not valid"); - } - - // reset the character from the stored data - character = JSON.parse(message.blob[0].data); - elizaLogger.info("Restore Character from blockchain"); - - return character; - } - - async getAllBlobHeaders(): Promise { + private async getAllBlobHeaders(): Promise { let headers: BlobHeader[] = []; + const count = this.parseRecoveryCount(process.env.BLOCKSTORE_RECOVERY_BLOB_COUNT, Number.MAX_SAFE_INTEGER); - let prev = await new Registry().getValue(this.id); + let prev = await new Registry().getBlobIdx(this.id); if (!prev || prev.trim() === "") { throw new Error(`Agent id ${this.id} not found on chain`); } @@ -106,7 +90,7 @@ export class BlockStoreUtil { while(true) { const blobData = await this.blockChain.pull(prev); // read idx from value - const message: Message = JSON.parse(blobData); + const message: Message = JSON.parse(blobData.trim()); if (!message) { throw new Error("Detected invalid data on the blockchain"); } @@ -115,14 +99,25 @@ export class BlockStoreUtil { }); prev = message.prev; - if (prev == null || prev == "") { + if (prev === null || prev === "" || headers.length > count) { break; } } } catch (error) { - console.error('Error fetching values:', error); + elizaLogger.error('Error fetching values:', error); } return headers; } + + private parseRecoveryCount(envValue: string|undefined, defaultValue: number): number { + if (envValue !== undefined) { + const value = process.env[envValue]?.trim().toLowerCase(); + if (value === "all") return -1; + const parsed = parseInt(envValue, 10); + if (!isNaN(parsed) && parsed >= 0) return parsed; + } + + return defaultValue; + } } \ No newline at end of file diff --git a/packages/core/src/memory.ts b/packages/core/src/memory.ts index 2855002c706..9c321a1910c 100644 --- a/packages/core/src/memory.ts +++ b/packages/core/src/memory.ts @@ -189,7 +189,7 @@ export class MemoryManager implements IMemoryManager { unique ); - if (process.env.BLOCKSTORE_STORE == "true") { + if (process.env.BLOCKSTORE_STORE_MEMORY.toLowerCase() == "true") { this.runtime.blockStoreAdapter.enqueue(BlockStoreMsgType.memory, memory); } }