Weighted votes migrate in-place migrations (#8663)

* Weighted votes migrate in-place migrations

* Rename to pb.go

* Fix some lint

* Fix lint

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Amaury 2021-03-02 23:22:02 +01:00 committed by GitHub
parent 810605689d
commit 30f58b5662
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 439 additions and 24 deletions

View File

@ -17,5 +17,5 @@ func NewMigrator(keeper Keeper) Migrator {
// Migrate1to2 migrates from version 1 to 2.
func (m Migrator) Migrate1to2(ctx sdk.Context) error {
return v042.MigrateStore(ctx, m.keeper.storeKey)
return v042.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc)
}

302
x/gov/legacy/v040/gov.pb.go Normal file
View File

@ -0,0 +1,302 @@
// Package v040 is take from:
// https://github.com/cosmos/cosmos-sdk/blob/v0.41.1/x/gov/types/gov.pb.go
// by copy-pasted only the relevants parts for Vote.
package v040
import (
"fmt"
"io"
math_bits "math/bits"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)
type Vote struct {
ProposalId uint64 `protobuf:"varint,1,opt,name=proposal_id,json=proposalId,proto3" json:"proposal_id,omitempty" yaml:"proposal_id"` //nolint:golint
Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty"`
Option types.VoteOption `protobuf:"varint,3,opt,name=option,proto3,enum=cosmos.gov.v1beta1.VoteOption" json:"option,omitempty"`
}
func (m *Vote) Reset() { *m = Vote{} }
func (*Vote) ProtoMessage() {}
func (m *Vote) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.ProposalId != 0 {
n += 1 + sovGov(m.ProposalId)
}
l = len(m.Voter)
if l > 0 {
n += 1 + l + sovGov(uint64(l))
}
if m.Option != 0 {
n += 1 + sovGov(uint64(m.Option))
}
return n
}
func (m *Vote) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Vote) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Vote) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.Option != 0 {
i = encodeVarintGov(dAtA, i, uint64(m.Option))
i--
dAtA[i] = 0x18
}
if len(m.Voter) > 0 {
i -= len(m.Voter)
copy(dAtA[i:], m.Voter)
i = encodeVarintGov(dAtA, i, uint64(len(m.Voter)))
i--
dAtA[i] = 0x12
}
if m.ProposalId != 0 {
i = encodeVarintGov(dAtA, i, m.ProposalId)
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func encodeVarintGov(dAtA []byte, offset int, v uint64) int {
offset -= sovGov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func sovGov(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func (m *Vote) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGov
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Vote: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Vote: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ProposalId", wireType)
}
m.ProposalId = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGov
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ProposalId |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Voter", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGov
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGov
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGov
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Voter = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Option", wireType)
}
m.Option = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGov
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Option |= types.VoteOption(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipGov(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthGov
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipGov(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGov
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGov
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowGov
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthGov
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupGov
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthGov
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthGov = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowGov = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupGov = fmt.Errorf("proto: unexpected end of group")
)

78
x/gov/legacy/v040/vote.go Normal file
View File

@ -0,0 +1,78 @@
// Package v040 is copy-pasted from:
// https://github.com/cosmos/cosmos-sdk/blob/v0.41.1/x/gov/types/vote.go
package v040
import (
"fmt"
yaml "gopkg.in/yaml.v2"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)
// NewVote creates a new Vote instance
//nolint:interfacer
func NewVote(proposalID uint64, voter sdk.AccAddress, option types.VoteOption) Vote {
return Vote{proposalID, voter.String(), option}
}
func (v Vote) String() string {
out, _ := yaml.Marshal(v)
return string(out)
}
// Votes is a collection of Vote objects
type Votes []Vote
// Equal returns true if two slices (order-dependant) of votes are equal.
func (v Votes) Equal(other Votes) bool {
if len(v) != len(other) {
return false
}
for i, vote := range v {
if vote.String() != other[i].String() {
return false
}
}
return true
}
func (v Votes) String() string {
if len(v) == 0 {
return "[]"
}
out := fmt.Sprintf("Votes for Proposal %d:", v[0].ProposalId)
for _, vot := range v {
out += fmt.Sprintf("\n %s: %s", vot.Voter, vot.Option)
}
return out
}
// Empty returns whether a vote is empty.
func (v Vote) Empty() bool {
return v.String() == Vote{}.String()
}
// VoteOptionFromString returns a VoteOption from a string. It returns an error
// if the string is invalid.
func VoteOptionFromString(str string) (types.VoteOption, error) {
option, ok := types.VoteOption_value[str]
if !ok {
return types.OptionEmpty, fmt.Errorf("'%s' is not a valid vote option", str)
}
return types.VoteOption(option), nil
}
// ValidVoteOption returns true if the vote option is valid and false otherwise.
func ValidVoteOption(option types.VoteOption) bool {
if option == types.OptionYes ||
option == types.OptionAbstain ||
option == types.OptionNo ||
option == types.OptionNoWithVeto {
return true
}
return false
}

View File

@ -1,10 +1,12 @@
package v042
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
v040gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v040"
"github.com/cosmos/cosmos-sdk/x/gov/types"
)
const proposalIDLen = 8
@ -30,14 +32,41 @@ func migratePrefixProposalAddress(store sdk.KVStore, prefixBz []byte) {
}
}
// migrateWeightedVotes migrates the ADR-037 weighted votes.
func migrateWeightedVotes(store sdk.KVStore, cdc codec.BinaryMarshaler) error {
iterator := sdk.KVStorePrefixIterator(store, v040gov.VotesKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var oldVote v040gov.Vote
err := cdc.UnmarshalBinaryBare(iterator.Value(), &oldVote)
if err != nil {
return err
}
newVote := &types.Vote{
ProposalId: oldVote.ProposalId,
Voter: oldVote.Voter,
Options: []types.WeightedVoteOption{{Option: oldVote.Option, Weight: sdk.NewDec(1)}},
}
bz, err := cdc.MarshalBinaryBare(newVote)
if err != nil {
return err
}
store.Set(iterator.Key(), bz)
}
return nil
}
// MigrateStore performs in-place store migrations from v0.40 to v0.42. The
// migration includes:
//
// - Change addresses to be length-prefixed.
func MigrateStore(ctx sdk.Context, storeKey sdk.StoreKey) error {
func MigrateStore(ctx sdk.Context, storeKey sdk.StoreKey, cdc codec.BinaryMarshaler) error {
store := ctx.KVStore(storeKey)
migratePrefixProposalAddress(store, v040gov.DepositsKeyPrefix)
migratePrefixProposalAddress(store, v040gov.VotesKeyPrefix)
return nil
return migrateWeightedVotes(store, cdc)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -16,6 +17,7 @@ import (
)
func TestStoreMigration(t *testing.T) {
cdc := simapp.MakeTestEncodingConfig().Marshaler
govKey := sdk.NewKVStoreKey("gov")
ctx := testutil.DefaultContext(govKey, sdk.NewTransientStoreKey("transient_test"))
store := ctx.KVStore(govKey)
@ -23,53 +25,57 @@ func TestStoreMigration(t *testing.T) {
_, _, addr1 := testdata.KeyTestPubAddr()
proposalID := uint64(6)
now := time.Now()
// Use dummy value for all keys.
value := []byte("foo")
// Use dummy value for keys where we don't test values.
dummyValue := []byte("foo")
// Use real values for votes, as we're testing weighted votes.
oldVote := v040gov.Vote{ProposalId: 1, Voter: "foobar", Option: types.OptionNoWithVeto}
oldVoteValue := cdc.MustMarshalBinaryBare(&oldVote)
newVote := types.Vote{ProposalId: 1, Voter: "foobar", Options: types.WeightedVoteOptions{{Option: types.OptionNoWithVeto, Weight: sdk.NewDec(1)}}}
newVoteValue := cdc.MustMarshalBinaryBare(&newVote)
testCases := []struct {
name string
oldKey []byte
newKey []byte
name string
oldKey, oldValue, newKey, newValue []byte
}{
{
"ProposalKey",
v040gov.ProposalKey(proposalID),
types.ProposalKey(proposalID),
v040gov.ProposalKey(proposalID), dummyValue,
types.ProposalKey(proposalID), dummyValue,
},
{
"ActiveProposalQueue",
v040gov.ActiveProposalQueueKey(proposalID, now),
types.ActiveProposalQueueKey(proposalID, now),
v040gov.ActiveProposalQueueKey(proposalID, now), dummyValue,
types.ActiveProposalQueueKey(proposalID, now), dummyValue,
},
{
"InactiveProposalQueue",
v040gov.InactiveProposalQueueKey(proposalID, now),
types.InactiveProposalQueueKey(proposalID, now),
v040gov.InactiveProposalQueueKey(proposalID, now), dummyValue,
types.InactiveProposalQueueKey(proposalID, now), dummyValue,
},
{
"ProposalIDKey",
v040gov.ProposalIDKey,
types.ProposalIDKey,
v040gov.ProposalIDKey, dummyValue,
types.ProposalIDKey, dummyValue,
},
{
"DepositKey",
v040gov.DepositKey(proposalID, addr1),
types.DepositKey(proposalID, addr1),
v040gov.DepositKey(proposalID, addr1), dummyValue,
types.DepositKey(proposalID, addr1), dummyValue,
},
{
"VotesKeyPrefix",
v040gov.VoteKey(proposalID, addr1),
types.VoteKey(proposalID, addr1),
v040gov.VoteKey(proposalID, addr1), oldVoteValue,
types.VoteKey(proposalID, addr1), newVoteValue,
},
}
// Set all the old keys to the store
for _, tc := range testCases {
store.Set(tc.oldKey, value)
store.Set(tc.oldKey, tc.oldValue)
}
// Run migrations.
err := v042gov.MigrateStore(ctx, govKey)
err := v042gov.MigrateStore(ctx, govKey, cdc)
require.NoError(t, err)
// Make sure the new keys are set and old keys are deleted.
@ -79,7 +85,7 @@ func TestStoreMigration(t *testing.T) {
if bytes.Compare(tc.oldKey, tc.newKey) != 0 {
require.Nil(t, store.Get(tc.oldKey))
}
require.Equal(t, value, store.Get(tc.newKey))
require.Equal(t, tc.newValue, store.Get(tc.newKey))
})
}
}