上一节,我们实现了一个链上数据存储器,这一节,我们将部署程序,并尝试与程序交互和升级程序
使用下面的命令编译程序代码.
$ 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 上部署一个程序和其他区块链(如以太坊)相比, 流程略有不同. 部署过程分为多个步骤, 每一步基本上对应一个或多个交易:
虽然流程复杂一些, 但这是高性能设计的一部分.
现在, 我们的链上数据存储器已经被部署在 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 早期负责管理程序的程序叫做 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
所谓的升级程序, 其实就是修改第二个账户的内容.