Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions contract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,11 @@ func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliv
return &PluginDeliverResponse{Error: err}
}
// ensure no error fsm error
// NOTE: must return response.Error here, not err (err is nil at this point since it was
// already checked above) — otherwise a failed state read is silently discarded and the
// function falls through to unmarshal zero-value bytes as if the accounts didn't error.
if response.Error != nil {
return &PluginDeliverResponse{Error: err}
return &PluginDeliverResponse{Error: response.Error}
}
// get the from bytes and to bytes
for _, resp := range response.Results {
Expand Down Expand Up @@ -189,11 +192,16 @@ func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliv
var resp *PluginStateWriteResponse
// if the from account is drained - delete the from account
if from.Amount == 0 {
sets := []*PluginSetOp{{Key: feePoolKey, Value: feePoolBytes}}
// for a self-transfer that drains the balance to zero, fromKey == toKey. Including a
// Set and a Delete for the identical key in the same write batch is undefined/order
// dependent in the underlying store, which risks non-deterministic state across nodes.
// Only include the 'to' Set when it targets a distinct account.
if !bytes.Equal(fromKey, toKey) {
sets = append(sets, &PluginSetOp{Key: toKey, Value: toBytes})
}
resp, err = c.plugin.StateWrite(c, &PluginStateWriteRequest{
Sets: []*PluginSetOp{
{Key: feePoolKey, Value: feePoolBytes},
{Key: toKey, Value: toBytes},
},
Sets: sets,
Deletes: []*PluginDeleteOp{{Key: fromKey}},
})
} else {
Expand Down