Cross Chain Access Control by SDK¶
In this tutorial we’ll use the go-SDK library to transfer control over objects to the smart contract on BSC and allowing on-chain management. Object mirroring enables greater flexibility and control over decentralized storage on BNB Greenfield to all dApps on BSC. It leverages the capabilities of the BSC and its smart contract functionality to provide enhanced functionality and interoperability between the two platforms.
Prerequisites¶
Before getting started, you should be familiar with:
- Greenfield basics
- Greenfield command line examples
Cross Chain Mechanism¶
Cross-chain communication serves as the foundation for enabling the exchange of assets, data, and functionalities across disparate blockchains, facilitating a more connected and efficient decentralised ecosystem.
Cross-communication between BNB Greenfield and BSC stands apart from the approaches taken by Polkadot, Chainlink, and Cosmos in several significant aspects.
Cross chain communication features | BNB Greenfield/BSC | Cosmos/IBC | Polkadot | Chainlink CCIP |
---|---|---|---|---|
Bulk messaging | Custom and performant | General application | General application | General application |
Compatibility | Fully compatible with EVM and Ethereum L2s | Only Cosmos ecosystem | Only Polkadot ecosystem | Specific implementations for each blockchain |
Security Model | Own validators | Shared | Shared | Own validators |
Tokenomics | BNB | ATOM | DOT | LINK |
Address Scheme | Unified - same addresses | Can be different addresses | Can be different addresses | Can be different addresses |
Composability | Shared components with BNB Chain ecosystem | Implementation in progress | Shared components with Polkadot ecosystem | New implementation for each network |
Account Setup¶
Create a Go Project¶
Let’s set up a Go project with the necessary dependencies.
Init¶
$ mkdir ~/hellogreenfield
$ cd ~/hellogreenfield
$ go mod init hellogreenfield
Add SDK Dependencies¶
$ go get github.com/bnb-chain/greenfield-go-sdk
Edit go.mod to replace dependencies
replace (
cosmossdk.io/api => github.com/bnb-chain/greenfield-cosmos-sdk/api v0.0.0-20230425074444-eb5869b05fe9
cosmossdk.io/math => github.com/bnb-chain/greenfield-cosmos-sdk/math v0.0.0-20230425074444-eb5869b05fe9
github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-cometbft v0.0.2
github.com/cometbft/cometbft-db => github.com/bnb-chain/greenfield-cometbft-db v0.8.1-alpha.1
github.com/cosmos/cosmos-sdk => github.com/bnb-chain/greenfield-cosmos-sdk v0.2.3
github.com/cosmos/iavl => github.com/bnb-chain/greenfield-iavl v0.20.1-alpha.1
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
)
Install dependensies¶
go mod tidy
Test a simple function¶
You can refer to the overview to learn about how to create a simple main.go
If everything is set up correctly, your code will be able to connect to the Greenfield node and return the chain data as shown above.
Account setup¶
account, err := types.NewAccountFromPrivateKey("test", privateKey)
if err != nil {
log.Fatalf("New account from private key error, %v", err)
}
cli, err := client.New(chainId, rpcAddr, client.Option{DefaultAccount: account})
if err != nil {
log.Fatalf("unable to new greenfield client, %v", err)
}
ctx := context.Background()
Create Buckets¶
Now, let’s use the imported account to create a bucket.
In this example,
// get storage providers list
spLists, err := cli.ListStorageProviders(ctx, true)
if err != nil {
log.Fatalf("fail to list in service sps")
}
// choose the first sp to be the primary SP
primarySP := spLists[0].GetOperatorAddress()
bucketName := storageTestUtil.GenRandomBucketName()
txHash, err := cli.CreateBucket(ctx, bucketName, primarySP, types.CreateBucketOptions{})
handleErr(err, "CreateBucket")
log.Printf("create bucket %s on SP: %s successfully \n", bucketName, spLists[0].Endpoint)
waitForTx, _ := cli.WaitForTx(ctx, txHash)
log.Printf("Wait for tx: %s", waitForTx.TxResult.String())
The example return message is like the following:
2023/10/31 13:14:54 create bucket ylatitsb on SP: https://gnfd-testnet-sp1.bnbchain.org successfully
2023/10/31 13:14:54 Wait for tx: data:"\0225\n+/greenfield.storage.MsgCreateBucketResponse\022\006\n\0043175\032\010\000\000\000\000\000\000\201\006" log:"[{\"msg_index\":0,\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/greenfield.storage.MsgCreateBucket\"},{\"key\":\"sender\",\"value\":\"0x525482AB3922230e4D73079890dC905dCc3D37cd\"},{\"key\":\"module\",\"value\":\"storage\"}]},{\"type\":\"greenfield.storage.EventCreateBucket\",\"attributes\":[{\"key\":\"bucket_id\",\"value\":\"\\\"3175\\\"\"},{\"key\":\"bucket_name\",\"value\":\"\\\"ylatitsb\\\"\"},{\"key\":\"charged_read_quota\",\"value\":\"\\\"0\\\"\"},{\"key\":\"create_at\",\"value\":\"\\\"1698779691\\\"\"},{\"key\":\"global_virtual_group_family_id\",\"value\":\"40\"},{\"key\":\"owner\",\"value\":\"\\\"0x525482AB3922230e4D73079890dC905dCc3D37cd\\\"\"},{\"key\":\"payment_address\",\"value\":\"\\\"0x525482AB3922230e4D73079890dC905dCc3D37cd\\\"\"},{\"key\":\"primary_sp_id\",\"value\":\"1\"},{\"key\":\"source_type\",\"value\":\"\\\"SOURCE_TYPE_ORIGIN\\\"\"},{\"key\":\"status\",\"value\":\"\\\"BUCKET_STATUS_CREATED\\\"\"},{\"key\":\"visibility\",\"value\":\"\\\"VISIBILITY_TYPE_PRIVATE\\\"\"}]}]}]" gas_wanted:2400 gas_used:2400 events:<type:"coin_spent" attributes:<key:"spender" value:"0x525482AB3922230e4D73079890dC905dCc3D37cd" index:true > attributes:<key:"amount" value:"12000000000000BNB" index:true > > events:<type:"coin_received" attributes:<key:"receiver" value:"0xf1829676DB577682E944fc3493d451B67Ff3E29F" index:true > attributes:<key:"amount" value:"12000000000000BNB" index:true > > events:<type:"transfer" attributes:<key:"recipient" value:"0xf1829676DB577682E944fc3493d451B67Ff3E29F" index:true > attributes:<key:"sender" value:"0x525482AB3922230e4D73079890dC905dCc3D37cd" index:true > attributes:<key:"amount" value:"12000000000000BNB" index:true > > events:<type:"message" attributes:<key:"sender" value:"0x525482AB3922230e4D73079890dC905dCc3D37cd" index:true > > events:<type:"tx" attributes:<key:"fee" value:"12000000000000BNB" index:true > attributes:<key:"fee_payer" value:"0x525482AB3922230e4D73079890dC905dCc3D37cd" index:true > > events:<type:"tx" attributes:<key:"acc_seq" value:"0x525482AB3922230e4D73079890dC905dCc3D37cd/70" index:true > > events:<type:"tx" attributes:<key:"signature" value:"aKL7wpB1b0107d1OleaHKKBw5mXUskggINbq7hsr90s6MzgV88DxjAGak37xz9V4LsoH0sr7saqBmBrE5MKJtgA=" index:true > > events:<type:"message" attributes:<key:"action" value:"/greenfield.storage.MsgCreateBucket" index:true > attributes:<key:"sender" value:"0x525482AB3922230e4D73079890dC905dCc3D37cd" index:true > attributes:<key:"module" value:"storage" index:true > > events:<type:"greenfield.storage.EventCreateBucket" attributes:<key:"bucket_id" value:"\"3175\"" index:true > attributes:<key:"bucket_name" value:"\"ylatitsb\"" index:true > attributes:<key:"charged_read_quota" value:"\"0\"" index:true > attributes:<key:"create_at" value:"\"1698779691\"" index:true > attributes:<key:"global_virtual_group_family_id" value:"40" index:true > attributes:<key:"owner" value:"\"0x525482AB3922230e4D73079890dC905dCc3D37cd\"" index:true > attributes:<key:"payment_address" value:"\"0x525482AB3922230e4D73079890dC905dCc3D37cd\"" index:true > attributes:<key:"primary_sp_id" value:"1" index:true > attributes:<key:"source_type" value:"\"SOURCE_TYPE_ORIGIN\"" index:true > attributes:<key:"status" value:"\"BUCKET_STATUS_CREATED\"" index:true > attributes:<key:"visibility" value:"\"VISIBILITY_TYPE_PRIVATE\"" index:true > >
- Query the bucket with
HeadBucket
functionThe example return message is like the following:// head bucket bucketInfo, err := cli.HeadBucket(ctx, bucketName) handleErr(err, "HeadBucket") log.Println("bucket info:", bucketInfo.String())
2023/10/31 13:14:54 bucket info: owner:"0x525482AB3922230e4D73079890dC905dCc3D37cd" bucket_name:"ylatitsb" visibility:VISIBILITY_TYPE_PRIVATE id:"3175" create_at:1698779691 payment_address:"0x525482AB3922230e4D73079890dC905dCc3D37cd" global_virtual_group_family_id:40
Create Group¶
The next step is to create a group, whose member will receive get object
access from the principla account.
// create group
groupTx, err := cli.CreateGroup(ctx, groupName, types.CreateGroupOptions{})
handleErr(err, "CreateGroup")
_, err = cli.WaitForTx(ctx, groupTx)
if err != nil {
log.Fatalln("txn fail")
}
log.Printf("create group %s successfully \n", groupName)
// head group info
creator, err := cli.GetDefaultAccount()
handleErr(err, "GetDefaultAccount")
groupInfo, err := cli.HeadGroup(ctx, groupName, creator.GetAddress().String())
handleErr(err, "HeadGroup")
log.Println("head group info:", groupInfo.String())
_, err = sdk.AccAddressFromHexUnsafe(memberAddress)
if err != nil {
log.Fatalln("the group member is invalid")
}
// add group member
updateTx, err := cli.UpdateGroupMember(ctx, groupName, creator.GetAddress().String(), []string{memberAddress}, []string{},
types.UpdateGroupMemberOption{})
handleErr(err, "UpdateGroupMember")
_, err = cli.WaitForTx(ctx, updateTx)
if err != nil {
log.Fatalln("txn fail")
}
log.Printf("add group member: %s to group: %s successfully \n", memberAddress, groupName)
// head group member
memIsExist := cli.HeadGroupMember(ctx, groupName, creator.GetAddress().String(), memberAddress)
if !memIsExist {
log.Fatalf("head group member %s fail \n", memberAddress)
}
log.Printf(" head member %s exist \n", memberAddress)
The result should look something similar to the following:
2023/10/31 09:34:54 create group sample-group successfully
2023/10/31 09:34:54 head group info: owner:"0x525482AB3922230e4D73079890dC905dCc3D37cd" group_name:"sample-group" id:"720"
2023/10/31 09:35:01 add group member: 0x843e77D639b6C382e91ef489881963209cB238E5 to group: sample-group successfully
2023/10/31 09:35:01 head member 0x843e77D639b6C382e91ef489881963209cB238E5 exist
Create Policy¶
Now, you can let the principal grants the get object
access to this group
// put bucket policy
bucketActions := []permTypes.ActionType{
permTypes.ACTION_GET_OBJECT,
}
ctx := context.Background()
statements := utils.NewStatement(bucketActions, permTypes.EFFECT_ALLOW, nil, types.NewStatementOptions{})
policyTx, err := cli.PutBucketPolicy(ctx, bucketName, principalStr, []*permTypes.Statement{&statements},
types.PutPolicyOption{})
handleErr(err, "PutBucketPolicy")
_, err = cli.WaitForTx(ctx, policyTx)
if err != nil {
log.Fatalln("txn fail")
}
log.Printf("put bucket %s policy sucessfully, principal is: %s.\n", bucketName, principal)
After you run the code, the result should look something similar to the following:
2023/10/31 10:46:55 put bucket sdkexamplebucket policy sucessfully, principal is:
2023/10/31 10:46:55 bucket: sdkexamplebucket policy info:id:"2358" principal:<type:PRINCIPAL_TYPE_GNFD_ACCOUNT value:"0x843e77D639b6C382e91ef489881963209cB238E5" > resource_type:RESOURCE_TYPE_BUCKET resource_id:"429" statements:<effect:EFFECT_ALLOW actions:ACTION_UPDATE_BUCKET_INFO actions:ACTION_DELETE_BUCKET actions:ACTION_DELETE_OBJECT actions:ACTION_GET_OBJECT >
Mirror Group to BSC¶
In Greenfield, object mirroring refers to the process of transferring control over objects stored on BNB Greenfield to a smart contract on BNB Smart Chain (BSC)
This allows the object to be fully managed on-chain on BSC, meaning that users or other smart contracts can perform various operations and changes to the object through on-chain transactions.
During the mirroring process from BNB Greenfield to BSC, the content of the file itself is not copied. This means that neither the data nor the file metadata, which is stored on the BNB Greenfield blockchain, is transferred to BSC.
//head group
groupInfo, err := cli.HeadGroup(ctx, groupName, creator.GetAddress().String())
handleErr(err, "HeadGroup")
log.Println("head group info:", groupInfo.String())
// mirror bucket
txResp, err := cli.MirrorGroup(ctx, sdk.ChainID(crossChainDestBsChainId), groupInfo.Id, groupName, gnfdSdkTypes.TxOption{})
handleErr(err, "MirrorGroup")
waitForTx, _ = cli.WaitForTx(ctx, txResp.TxHash)
log.Printf("Wait for tx: %s", waitForTx.TxResult.String())
log.Printf("successfully mirrored group wiht id %s to BSC", groupInfo.Id)
2023/10/31 21:43:57 group: sdkexamplegroup policy info:id:"712" principal:<type:PRINCIPAL_TYPE_GNFD_ACCOUNT value:"0x843e77D639b6C382e91ef489881963209cB238E5" > resource_type:RESOURCE_TYPE_BUCKET resource_id:"429" statements:<effect:EFFECT_ALLOW actions:ACTION_GET_OBJECT >
2023/10/31 21:43:57 bucket info: owner:"0x525482AB3922230e4D73079890dC905dCc3D37cd" bucket_name:"ylatitsb" visibility:VISIBILITY_TYPE_PRIVATE id:"3175" create_at:1698779691 payment_address:"0x525482AB3922230e4D73079890dC905dCc3D37cd" global_virtual_group_family_id:40
You can also inspect using the block scanner, e.g. https://greenfieldscan.com.
Access Control Management on BSC¶
Now you have mirrored your group to BSC and there is an ERCC-721 token minted. At present, the NFTs are not transferable. The group membership can be directly managed by smart contracts on BSC. These operations will directly affect the storage format, access permissions, and other aspects of the data on greenfield with the help of Greenfield Contract.
First, you have to install the dependencies and setup environment by following the guides.
Once it’s all set, you can run the following script to add member to your group:
# set your private-key, operator address, group id, and member address
forge script foundry-scripts/GroupHub.s.sol:GroupHubScript \
--private-key ${your private key} \
--sig "addMember(address operator, uint256 groupId, address member)" \
${the owner of the group} ${your group id} ${the member address to add} \
-f https://data-seed-prebsc-1-s1.binance.org:8545/ \
--legacy --ffi --broadcast
Conclusion¶
The Greenfield Blockchain provides a comprehensive set of resources that can be mirrored on the BNB Smart Chain (BSC). This includes buckets, objects, and groups, which can be stored and managed on the BSC as non-fungible tokens (NFTs) conforming to the ERC-721 standard. This integration between Greenfield Blockchain and BNB Smart Chain allows for greater flexibility and accessibility when it comes to accessing and manipulating data, ultimately leading to a more streamlined and efficient data management process.