我应该如何处理 NEAR 协议中的智能合约结构变更?
How should I handle smart contract structure change in NEAR protocol?
假设我有合同
...
pub struct Contract {
collection_a: Vector<String>,
}
部署此版本后,当我更改智能合约的数据结构时,例如
pub struct Contract {
collection_a: Vector<String>,
collection_b: Vector<String>,
}
我运行在与合约交互时遇到错误
Failure [dev-1644158197214-15380220543819]: Error: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46"}}
ServerTransactionError: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46"}}
at Object.parseResultError (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/node_modules/near-api-js/lib/utils/rpc_errors.js:31:29)
at Account.signAndSendTransactionV2 (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/node_modules/near-api-js/lib/account.js:160:36)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async scheduleFunctionCall (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/commands/call.js:57:38)
at async Object.handler (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/utils/exit-on-error.js:52:9) {
type: 'FunctionCallError',
context: undefined,
index: 0,
kind: {
ExecutionError: `Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: "Unexpected length of input" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46`
},
transaction_outcome: {
proof: [ [Object], [Object] ],
block_hash: '5mPRmggsyL9cNsgS4a6mzRT7ua9Y8SS8XJbW9psawdDr',
id: '8BeARer3UXLoZ3Vr22QAqkyzsp143D7FCtVssjyYxzs',
outcome: {
logs: [],
receipt_ids: [Array],
gas_burnt: 2427936651538,
tokens_burnt: '242793665153800000000',
executor_id: 'dev-1644158197214-15380220543819',
status: [Object],
metadata: [Object]
}
}
}
当我需要更新结构时,如何处理这种情况?
你要的是storage migration:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct OldContract {
collection_a: Vector<String>,
}
#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Contract {
collection_a: Vector<String>,
collection_b: Vector<String>,
}
#[near_bindgen]
impl Contract {
#[private]
#[init(ignore_state)]
pub fn migrate() -> Self {
let old_storage: OldContract = env::state_read().expect("Couldn't read old state");
Self {
collection_a: old_storage.collection_a,
collection_b: Vec::new(),
}
}
}
首先使用此代码更新合同,然后使用合同密钥调用 migrate
方法。在下次升级时,您可以删除方法和 OldContract
结构以节省存储空间。
您可能 运行 遇到的一个问题是不适合单个块的存储迁移。 AFAIK,没有解决方案。然而,Borsh 序列化是确定性的,因此只要您将数据结构保持为枚举,您就应该能够重新解释当前链存储并将迁移拆分为多个部分迁移。确保彻底测试,你 运行 不可恢复地搞砸你的合同状态的风险。
假设我有合同
...
pub struct Contract {
collection_a: Vector<String>,
}
部署此版本后,当我更改智能合约的数据结构时,例如
pub struct Contract {
collection_a: Vector<String>,
collection_b: Vector<String>,
}
我运行在与合约交互时遇到错误
Failure [dev-1644158197214-15380220543819]: Error: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46"}}
ServerTransactionError: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: \"Unexpected length of input\" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46"}}
at Object.parseResultError (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/node_modules/near-api-js/lib/utils/rpc_errors.js:31:29)
at Account.signAndSendTransactionV2 (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/node_modules/near-api-js/lib/account.js:160:36)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async scheduleFunctionCall (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/commands/call.js:57:38)
at async Object.handler (/home/gitpod/.nvm/versions/node/v16.13.0/lib/node_modules/near-cli/utils/exit-on-error.js:52:9) {
type: 'FunctionCallError',
context: undefined,
index: 0,
kind: {
ExecutionError: `Smart contract panicked: panicked at 'Cannot deserialize the contract state.: Custom { kind: InvalidInput, error: "Unexpected length of input" }', /workspace/.cargo/registry/src/github.com-1ecc6299db9ec823/near-sdk-3.1.0/src/environment/env.rs:786:46`
},
transaction_outcome: {
proof: [ [Object], [Object] ],
block_hash: '5mPRmggsyL9cNsgS4a6mzRT7ua9Y8SS8XJbW9psawdDr',
id: '8BeARer3UXLoZ3Vr22QAqkyzsp143D7FCtVssjyYxzs',
outcome: {
logs: [],
receipt_ids: [Array],
gas_burnt: 2427936651538,
tokens_burnt: '242793665153800000000',
executor_id: 'dev-1644158197214-15380220543819',
status: [Object],
metadata: [Object]
}
}
}
当我需要更新结构时,如何处理这种情况?
你要的是storage migration:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct OldContract {
collection_a: Vector<String>,
}
#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Contract {
collection_a: Vector<String>,
collection_b: Vector<String>,
}
#[near_bindgen]
impl Contract {
#[private]
#[init(ignore_state)]
pub fn migrate() -> Self {
let old_storage: OldContract = env::state_read().expect("Couldn't read old state");
Self {
collection_a: old_storage.collection_a,
collection_b: Vec::new(),
}
}
}
首先使用此代码更新合同,然后使用合同密钥调用 migrate
方法。在下次升级时,您可以删除方法和 OldContract
结构以节省存储空间。
您可能 运行 遇到的一个问题是不适合单个块的存储迁移。 AFAIK,没有解决方案。然而,Borsh 序列化是确定性的,因此只要您将数据结构保持为枚举,您就应该能够重新解释当前链存储并将迁移拆分为多个部分迁移。确保彻底测试,你 运行 不可恢复地搞砸你的合同状态的风险。