链上数据存储器程序部署、交互与升级 | Solana

上一节,我们实现了一个链上数据存储器,这一节,我们将部署程序,并尝试与程序交互和升级程序

编译并部署程序

编译

使用下面的命令编译程序代码.

$ cargo build-sbf -- -Znext-lockfile-bump

部署程序

使用下面的 python 代码部署目标程序上链:

import pathlib
import pxsol

# Enable log
pxsol.config.current.log = 1

ada = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(0x01))

program_data = pathlib.Path('target/deploy/pxsol_ss.so').read_bytes()
program_pubkey = ada.program_deploy(bytearray(program_data))
print(program_pubkey) # DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E

在部署过程中, 您会在日志中看到大量的交易. 在 solana 上部署一个程序和其他区块链(如以太坊)相比, 流程略有不同. 部署过程分为多个步骤, 每一步基本上对应一个或多个交易:

  1. 创建一个程序账户.
  2. 分段上传程序代码(分片写入). Solana 的单笔交易大小有限, 一个交易序列化后最多不超过 1232 字节, 而你的程序代码可能有几万字节或更多. 所以必须把 bpf 字节码分片后, 分多次交易写入账户的数据区.
  3. 所有字节都写完之后, 需要最后一步: 调用 bpf loader 程序的 finalize 方法, 把账户标记为 finalized. 从这个时候开始, 它才会变成一个真正的 solana 程序了.

虽然流程复杂一些, 但这是高性能设计的一部分.

程序交互

现在, 我们的链上数据存储器已经被部署在 DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E 地址上. 我们尝试写入自己的数据到程序里.

写入数据到账户

写数据的过程是通过一个 solana 交易来完成的. 您可以这样写入数据:

import base64
import pxsol

pxsol.config.current.log = 1

ada = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(0x01))

def save(user: pxsol.wallet.Wallet, data: bytearray) -> None:
    prog_pubkey = pxsol.core.PubKey.base58_decode('DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E')
    data_pubkey = prog_pubkey.derive_pda(user.pubkey.p)
    rq = pxsol.core.Requisition(prog_pubkey, [], bytearray())
    rq.account.append(pxsol.core.AccountMeta(user.pubkey, 3))
    rq.account.append(pxsol.core.AccountMeta(data_pubkey, 1))
    rq.account.append(pxsol.core.AccountMeta(pxsol.program.System.pubkey, 0))
    rq.account.append(pxsol.core.AccountMeta(pxsol.program.SysvarRent.pubkey, 0))
    rq.data = data
    tx = pxsol.core.Transaction.requisition_decode(user.pubkey, [rq])
    tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
    tx.sign([user.prikey])
    txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
    pxsol.rpc.wait([txid])
    r = pxsol.rpc.get_transaction(txid, {})
    for e in r['meta']['logMessages']:
        print(e)

if __name__ == '__main__':
    save(ada, b'The quick brown fox jumps over the lazy dog')
# 2025/05/27 10:17:23 pxsol: transaction send signature=oCF2esfLeM7iu8MsR5wgBPatVXGt9Dq7TSzLpwWuMjooeDBeHMtSc8ukuqmPcaMrzzHcdiLg7cPbPzsHi2vdv8j
# 2025/05/27 10:17:23 pxsol: transaction wait unconfirmed=1
# 2025/05/27 10:17:23 pxsol: transaction wait unconfirmed=0
# Program DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E invoke [1]
# Program DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E consumed 2903 of 200000 compute units
# Program DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E success

读取链上的数据

读取数据就是查询自己的 pda 数据账户中存储数据的过程. 这个过程不需要构造交易, 只需要借助 rpc 接口查询即可.

import base64
import pxsol

pxsol.config.current.log = 1

ada = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(0x01))

def load(user: pxsol.wallet.Wallet) -> bytearray:
    prog_pubkey = pxsol.core.PubKey.base58_decode('DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E')
    data_pubkey = prog_pubkey.derive_pda(user.pubkey.p)
    info = pxsol.rpc.get_account_info(data_pubkey.base58(), {})
    return base64.b64decode(info['data'][0])

if __name__ == '__main__':
    print(load(ada).decode()) # The quick brown fox jumps over the lazy dog

更新链上的数据

我们只需要重新调用一次 save() 并写入不同的数据, 就能覆盖链上已有的数据, 程序会为我们自动实现新的租赁豁免. 完整代码如下.

import base64
import pxsol

pxsol.config.current.log = 1

ada = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(0x01))

def save(user: pxsol.wallet.Wallet, data: bytearray) -> None:
    prog_pubkey = pxsol.core.PubKey.base58_decode('DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E')
    data_pubkey = prog_pubkey.derive_pda(user.pubkey.p)
    rq = pxsol.core.Requisition(prog_pubkey, [], bytearray())
    rq.account.append(pxsol.core.AccountMeta(user.pubkey, 3))
    rq.account.append(pxsol.core.AccountMeta(data_pubkey, 1))
    rq.account.append(pxsol.core.AccountMeta(pxsol.program.System.pubkey, 0))
    rq.account.append(pxsol.core.AccountMeta(pxsol.program.SysvarRent.pubkey, 0))
    rq.data = data
    tx = pxsol.core.Transaction.requisition_decode(user.pubkey, [rq])
    tx.message.recent_blockhash = pxsol.base58.decode(pxsol.rpc.get_latest_blockhash({})['blockhash'])
    tx.sign([user.prikey])
    txid = pxsol.rpc.send_transaction(base64.b64encode(tx.serialize()).decode(), {})
    pxsol.rpc.wait([txid])
    r = pxsol.rpc.get_transaction(txid, {})
    for e in r['meta']['logMessages']:
        print(e)

def load(user: pxsol.wallet.Wallet) -> bytearray:
    prog_pubkey = pxsol.core.PubKey.base58_decode('DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E')
    data_pubkey = prog_pubkey.derive_pda(user.pubkey.p)
    info = pxsol.rpc.get_account_info(data_pubkey.base58(), {})
    return base64.b64decode(info['data'][0])

if __name__ == '__main__':
    save(ada, b'The quick brown fox jumps over the lazy dog')
    print(load(ada).decode()) # The quick brown fox jumps over the lazy dog
    save(ada, '片云天共远, 永夜月同孤.'.encode())
    print(load(ada).decode()) # 片云天共远, 永夜月同孤.

升级程序

在 solana 中, 我们可以替换已部署的程序的代码. 不过要注意, solana 程序的部署和更新存在几个不同的版本演进, 在早期的版本里, 程序是不可升级的.

如何升级程序

升级程序可使用 pxsol.wallet.Wallet.program_update() 接口完成. 您可以修改我们的链上数据存储器代码, 然后使用如下的命令来升级链上程序版本. 升级程序不会修改程序的地址.

import pathlib
import pxsol

ada = pxsol.wallet.Wallet(pxsol.core.PriKey.int_decode(0x01))
program_pubkey = pxsol.core.PubKey.base58_decode('DVapU9kvtjzFdH3sRd3VDCXjZVkwBR6Cxosx36A5sK5E')
program_data = pathlib.Path('target/deploy/pxsol_ss.so').read_bytes()
ada.program_update(program_pubkey, program_data)

Solana 程序最早是不可更改的

Solana 早期负责管理程序的程序叫做 bpf loader, 也叫做 v1. 使用它部署的程序, 您的程序会被标记为所有数据不可写, 所有代码不可修改, 同时程序账户的 executable 被设置为 true. 所以这种部署方式下, 程序不能更新! 如果您想升级, 就只能部署一个新的程序, 并更新所有依赖它的账户或前端逻辑, 这无疑非常麻烦.

演进史

Solana 的 bpf loader 是一个特殊的原生程序, 专门用来加载并执行您上传的 bpf 字节码. Solana 发展过程中经历了三个主要版本的 loader:

Loader 名称 地址 特点
BPFLoader (v1) BPFLoader1111111111111111111111111111111111 最早期版本, 部署后不可升级
BPFLoader2 (v2) BPFLoader2111111111111111111111111111111111 支持更高效的加载, 但依旧不可升级
BPFLoaderUpgradeable (v3) BPFLoaderUpgradeab1e11111111111111111111111 当前默认, 支持程序升级和权限控制

也就是从 v3 开始, solana 引入了可升级程序的概念, 并将其作为默认部署方式.

可升级程序的结构图

Pxsol 默认使用 v3 来部署程序. 这会自动创建两个账户:

账户类型 作用
Program account 主地址, 对外暴露的程序入口
Program data account 存储实际代码字节码, 可变
Program ID (e.g., MyProgram111...)
│
├──> Program Account
│     └── owner: BPFLoaderUpgradeable
│     └── executable: true
│     └── points to:
│
└──> ProgramData Account
      └── contains: .so 字节码
      └── contains: upgrade_authority pubkey

所谓的升级程序, 其实就是修改第二个账户的内容.

点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论