Skip to content

Commit dbfbcf7

Browse files
authored
feat: Bulletin improvements (#108)
* Add UpdatePost msg, run make proto * Add update post msg type * Update bulletin policy * Update errors/constants * Update codec, add auto-cli * Add UpdatePost msg server, add tests * Add more tests
1 parent c7cf8e7 commit dbfbcf7

12 files changed

Lines changed: 1338 additions & 128 deletions

File tree

proto/sourcehub/bulletin/events.proto

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ message EventPostCreated {
3131
string artifact = 5;
3232
}
3333

34+
// EventPostUpdated is emitted when an existing post's payload is updated.
35+
// The post_id is unchanged across updates.
36+
message EventPostUpdated {
37+
// namespace_id is the namespace the post belongs to.
38+
string namespace_id = 1;
39+
// post_id is the unique identifier of the post (stable across updates).
40+
string post_id = 2;
41+
// updater_did is the DID of the actor that performed the update.
42+
string updater_did = 3;
43+
// payload is the base64-encoded new content of the post.
44+
string payload = 4;
45+
// artifact is sent with an update call for tracking purposes.
46+
string artifact = 5;
47+
}
48+
3449
// EventCollaboratorAdded is emitted when a collaborator is added to a namespace.
3550
message EventCollaboratorAdded {
3651
// namespace_id is the namespace the collaborator was added to.

proto/sourcehub/bulletin/tx.proto

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ service Msg {
1919
// parameters. The authority defaults to the x/gov module account.
2020
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
2121
rpc CreatePost(MsgCreatePost) returns (MsgCreatePostResponse);
22+
rpc UpdatePost(MsgUpdatePost) returns (MsgUpdatePostResponse);
2223
rpc RegisterNamespace(MsgRegisterNamespace) returns (MsgRegisterNamespaceResponse);
2324
rpc AddCollaborator(MsgAddCollaborator) returns (MsgAddCollaboratorResponse);
2425
rpc RemoveCollaborator(MsgRemoveCollaborator) returns (MsgRemoveCollaboratorResponse);
@@ -55,6 +56,17 @@ message MsgCreatePost {
5556

5657
message MsgCreatePostResponse {}
5758

59+
message MsgUpdatePost {
60+
option (cosmos.msg.v1.signer) = "creator";
61+
string creator = 1;
62+
string namespace = 2;
63+
string post_id = 3;
64+
bytes payload = 4;
65+
string artifact = 5;
66+
}
67+
68+
message MsgUpdatePostResponse {}
69+
5870
message MsgRegisterNamespace {
5971
option (cosmos.msg.v1.signer) = "creator";
6072
string creator = 1;

x/bulletin/keeper/msg_server.go

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,10 @@ func (k *Keeper) RegisterNamespace(goCtx context.Context, msg *types.MsgRegister
7171
}
7272

7373
// CreatePost adds a new post to the specified (existing) namespace.
74-
// The signer must have permission to create posts in that namespace.
74+
// Post creation is unrestricted: any valid signer may create a post in any
75+
// registered namespace. Duplicate (namespace, payload) pairs are rejected
76+
// because post_id is derived from them.
7577
func (k *Keeper) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) {
76-
policyId := k.GetPolicyId(goCtx)
77-
if policyId == "" {
78-
return nil, types.ErrInvalidPolicyId
79-
}
80-
8178
ctx := sdk.UnwrapSDKContext(goCtx)
8279

8380
namespaceId := getNamespaceId(msg.Namespace)
@@ -90,18 +87,9 @@ func (k *Keeper) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*t
9087
return nil, err
9188
}
9289

93-
hasPermission, err := hasPermission(goCtx, k, policyId, namespaceId, types.CreatePostPermission, creatorDID, msg.Creator)
94-
if err != nil {
95-
return nil, err
96-
}
97-
if !hasPermission {
98-
return nil, types.ErrInvalidPostCreator
99-
}
100-
10190
postId := types.GeneratePostId(namespaceId, msg.Payload)
10291

103-
existingPost := k.getPost(goCtx, namespaceId, postId)
104-
if existingPost != nil {
92+
if existing := k.getPost(goCtx, namespaceId, postId); existing != nil {
10593
return nil, types.ErrPostAlreadyExists
10694
}
10795

@@ -127,6 +115,60 @@ func (k *Keeper) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*t
127115
return &types.MsgCreatePostResponse{}, nil
128116
}
129117

118+
// UpdatePost overwrites the payload of an existing post while preserving the
119+
// post_id. Authorization: the signer must either be the original creator, or
120+
// be a `collaborator` on the post's namespace (checked via the module's ACP
121+
// policy `update_post` permission).
122+
func (k *Keeper) UpdatePost(goCtx context.Context, msg *types.MsgUpdatePost) (*types.MsgUpdatePostResponse, error) {
123+
ctx := sdk.UnwrapSDKContext(goCtx)
124+
125+
policyId := k.GetPolicyId(goCtx)
126+
if policyId == "" {
127+
return nil, types.ErrInvalidPolicyId
128+
}
129+
130+
namespaceId := getNamespaceId(msg.Namespace)
131+
if !k.hasNamespace(goCtx, namespaceId) {
132+
return nil, types.ErrNamespaceNotFound
133+
}
134+
135+
existing := k.getPost(goCtx, namespaceId, msg.PostId)
136+
if existing == nil {
137+
return nil, types.ErrPostNotFound
138+
}
139+
140+
updaterDID, err := k.GetAcpKeeper().GetActorDID(ctx, msg.Creator)
141+
if err != nil {
142+
return nil, err
143+
}
144+
145+
if updaterDID != existing.CreatorDid {
146+
allowed, err := hasPermission(goCtx, k, policyId, namespaceId, types.UpdatePostPermission, updaterDID, msg.Creator)
147+
if err != nil {
148+
return nil, err
149+
}
150+
if !allowed {
151+
return nil, types.ErrInvalidPostUpdater
152+
}
153+
}
154+
155+
existing.Payload = msg.Payload
156+
k.SetPost(goCtx, *existing)
157+
158+
b64Payload := base64.StdEncoding.EncodeToString(existing.Payload)
159+
if err := ctx.EventManager().EmitTypedEvent(&types.EventPostUpdated{
160+
NamespaceId: namespaceId,
161+
PostId: existing.Id,
162+
UpdaterDid: updaterDID,
163+
Payload: b64Payload,
164+
Artifact: msg.Artifact,
165+
}); err != nil {
166+
return nil, err
167+
}
168+
169+
return &types.MsgUpdatePostResponse{}, nil
170+
}
171+
130172
// AddCollaborator adds a new collaborator to the specified namespace.
131173
// The signer must have permission to manage collaborators of that namespace object.
132174
func (k *Keeper) AddCollaborator(goCtx context.Context, msg *types.MsgAddCollaborator) (*types.MsgAddCollaboratorResponse, error) {

0 commit comments

Comments
 (0)