From c4ef46bee7c30751d6bd3415507e0474527d58d9 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Thu, 16 May 2024 19:38:54 +0200 Subject: [PATCH 01/71] Add the initial /sys/vals PoS implementation --- examples/gno.land/r/sys/validators/gno.mod | 1 - .../gno.land/r/sys/validators/validators.gno | 4 - examples/gno.land/r/sys/vals/gno.mod | 6 + examples/gno.land/r/sys/vals/pos/option.gno | 20 ++ examples/gno.land/r/sys/vals/pos/pos.gno | 215 ++++++++++++++++++ examples/gno.land/r/sys/vals/types/types.gno | 17 ++ examples/gno.land/r/sys/vals/vals.gno | 60 +++++ 7 files changed, 318 insertions(+), 5 deletions(-) delete mode 100644 examples/gno.land/r/sys/validators/gno.mod delete mode 100644 examples/gno.land/r/sys/validators/validators.gno create mode 100644 examples/gno.land/r/sys/vals/gno.mod create mode 100644 examples/gno.land/r/sys/vals/pos/option.gno create mode 100644 examples/gno.land/r/sys/vals/pos/pos.gno create mode 100644 examples/gno.land/r/sys/vals/types/types.gno create mode 100644 examples/gno.land/r/sys/vals/vals.gno diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/gno.mod deleted file mode 100644 index 84df66b9001..00000000000 --- a/examples/gno.land/r/sys/validators/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/sys/validators diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno deleted file mode 100644 index dc9d731f075..00000000000 --- a/examples/gno.land/r/sys/validators/validators.gno +++ /dev/null @@ -1,4 +0,0 @@ -// This package is used to manage the validator set. -package validators - -// write specs. diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod new file mode 100644 index 00000000000..57c90c475af --- /dev/null +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals + +require ( + gno.land/p/demo/json v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/pos/option.gno b/examples/gno.land/r/sys/vals/pos/option.gno new file mode 100644 index 00000000000..8bf1b460d18 --- /dev/null +++ b/examples/gno.land/r/sys/vals/pos/option.gno @@ -0,0 +1,20 @@ +package pos + +import ( + "std" + + "gno.land/r/sys/vals/types" +) + +type Option func(*PoS) + +// WithInitialSet sets the initial PoS validator set, with empty stakes +func WithInitialSet(validators []types.Validator) Option { + return func(p *PoS) { + for index, validator := range validators { + p.validators = append(p.validators, validator) + p.addressToValidatorIndex[validator.Address] = index + p.addressToStakedAmount[validator.Address] = std.Coin{"ugnot", 0} // no stake + } + } +} diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno new file mode 100644 index 00000000000..6b4c1ca3ba3 --- /dev/null +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -0,0 +1,215 @@ +package pos + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/r/sys/vals/types" +) + +var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs + +const ( + minValidatorCount = 4 // the absolute minimum number of validators in the set + maxValidatorCount = 150 // the practical maximum number of validators in the set +) + +const ( + UserStakedEvent = "UserStaked" // emitted when a user stakes funds + UserUnstakedEvent = "UserUnstaked" // emitted when a user unstakes funds +) + +// PoS specifies the Proof of Stake validator set. +// In order to become part of the set, users need to stake funds (at least at the threshold). +// Upon leaving the set, users receive their staked funds back +type PoS struct { + // validators holds the current validator set. + // This slice can never practically grow more than ~150 elements, + // due to Tendermint's quadratic network complexity + validators []types.Validator + addressToValidatorIndex map[std.Address]int // address -> index + addressToStakedAmount map[std.Address]std.Coin // address -> staked amount +} + +// NewPoS creates a new empty Proof of Stake validator set +func NewPoS(opts ...Option) *PoS { + // Create the empty set + p := &PoS{ + validators: make([]types.Validator, 0), + addressToValidatorIndex: make(map[std.Address]int), + addressToStakedAmount: make(map[std.Address]std.Coin), + } + + // Apply the options + for _, opt := range opts { + opt(p) + } + + return p +} + +// AddValidator adds a new validator to the validator set. +// +// Criteria for being added: +// - caller is a user account +// - caller is not already a validator +// - caller has staked funds >= the current staking threshold +// - the validator set is less than the maximum size +func (p *PoS) AddValidator(address, pubKey string, votingPower uint64) { + // Check if the caller is a user account + if !std.IsOriginCall() { + panic("caller not user account") + } + + // Check if the validator is already in the set + if p.IsValidator(address) { + panic("validator already exists") + } + + // Check if the limit is reached + if len(p.validators) <= maxValidatorCount { + panic("maximum validator count reached") + } + + var ( + caller = std.GetOrigCaller() + stakedAmount = std.GetOrigSend().AmountOf("ugnot") + ) + + // Check if the caller is supplying their own address + if address != caller.String() { + panic("validator address is not origin") + } + + // Fetch the already staked amount + addressStake, exists := p.addressToStakedAmount[caller] + if !exists { + addressStake = std.Coin{"ugnot", int64(0)} + } + + // Save the staked amount + addressStake.Amount += stakedAmount + p.addressToStakedAmount[caller] = addressStake + + // Emit the event that the user staked funds + std.Emit( + UserStakedEvent, + "address", address, + "stake", ufmt.Sprintf("%d", stakedAmount.Amount), + ) + + // Check if the caller can become a validator + if !p.canBecomeValidator(caller) { + return + } + + // Add the caller to the validator set + v := types.Validator{ + Address: caller, + PubKey: pubKey, // TODO check public key? + VotingPower: votingPower, // TODO validate voting power? + } + + p.addressToValidatorIndex[caller] = len(p.validators) + p.validators = append(p.validators, v) + + // Emit the validator set change + std.Emit( + types.ValidatorAddedEvent, + "address", address, + ) +} + +// canBecomeValidator checks if the address fulfills all criteria for +// becoming a validator: +// - is not already a validator +// - has staked >= the staking threshold +func (p *PoS) canBecomeValidator(address std.Address) bool { + stake, _ := p.addressToStakedAmount[address] + + return !p.IsValidator(address.String()) && stake.IsAllGTE(stakeThreshold) +} + +// RemoveValidator removes a validator from the validator set. +// Upon successful removal, the staked funds are returned to the user +// +// Criteria for being removed: +// - caller is a user account +// - caller is a validator +// - the validator set is more than the minimum size +func (p *PoS) RemoveValidator(address string) { + // Check if the caller is a user account + if !std.IsOriginCall() { + panic("caller not user account") + } + + // Check if this request came from a validator + if !p.IsValidator(std.GetOrigCaller()) { + panic("user is not validator") + } + + // Check if the limit is reached + if len(p.validators) == minValidatorCount { + panic("minimum validator count reached") + } + + var ( + caller = std.GetOrigCaller() + addressStake = p.addressToStakedAmount[caller] + ) + + // Check if the caller is supplying their own address + if address != caller.String() { + panic("validator address is not origin") + } + + // Check if the validator is in the set + index := addressToValidatorIndex[caller] + + // Remove the validator from the set + p.validators = append(p.validators[:index], p.validators[index+1:]...) + + delete(p.addressToValidatorIndex, caller) + delete(p.addressToStakedAmount, caller) + + // Return the stake + returnStake(caller, addressStake) + + // Emit the validator set change + std.Emit( + types.ValidatorRemovedEvent, + "address", address, + ) + + // Emit the unstake event + std.Emit( + UserUnstakedEvent, + "address", address, + "stake", ufmt.Sprintf("%d", addressStake.Amount), + ) +} + +// returnStake returns the specified stake to the given address +func returnStake(address std.Address, amount std.Coin) { + // Derive the current package address + from := std.DerivePkgAddr(std.CurrentRealm()) + + // Fetch the banker + banker := std.GetBanker(std.BankerTypeRealmSend) + + // Return the staked funds + banker.SendCoins(from, address, std.Coins{amount}) +} + +// IsValidator returns a flag indicating if the address +// is part of the staked validator set +func (p *PoS) IsValidator(address string) bool { + _, exists := p.addressToValidatorIndex[std.Address(address)] + + return exists +} + +// GetValidators returns the current staked validator set +func (p *PoS) GetValidators() []types.Validator { + return p.validators +} diff --git a/examples/gno.land/r/sys/vals/types/types.gno b/examples/gno.land/r/sys/vals/types/types.gno new file mode 100644 index 00000000000..4d786debb92 --- /dev/null +++ b/examples/gno.land/r/sys/vals/types/types.gno @@ -0,0 +1,17 @@ +package types + +import ( + "std" +) + +// Validator represents a single chain validator +type Validator struct { + Address std.Address // bech32 address + PubKey string // bech32 representation of the public key + VotingPower uint64 +} + +const ( + ValidatorAddedEvent = "ValidatorAdded" // emitted when a validator was added to the set + ValidatorRemovedEvent = "ValidatorRemoved" // emitted when a validator was removed from the set +) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno new file mode 100644 index 00000000000..a7a4e99ff8d --- /dev/null +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -0,0 +1,60 @@ +package vals + +import ( + "gno.land/p/demo/json" + "gno.land/p/demo/ufmt" + "gno.land/p/sys/vals/pos" + "gno.land/p/sys/vals/types" +) + +// Protocol defines the validator set protocol (PoA / PoS) +type Protocol interface { + // AddValidator adds a new validator to the validator set. + // If the validator is already present, the method should error out + AddValidator(address, pubKey string, votingPower uint64) + + // RemoveValidator removes the given validator from the set. + // If the validator is not present in the set, the method should error out + RemoveValidator(address string) + + // IsValidator returns a flag indicating if the given + // bech32 address is part of the validator set + IsValidator(address string) bool + + // GetValidators returns the JSON-encoded validator set. + // The reason for returning JSON, and not a `[]Validator`, + // is because Gno does not yet support an ABI-like functionality + // for clients interacting with the Realm / Package methods + GetValidators() []types.Validator +} + +// p is the underlying validator set protocol (PoA / PoS) +var p Protocol = pos.NewPoS() + +// AddValidator adds a new validator to the validator set. +// If the validator is already present, the method errors out +func AddValidator(address, pubKey string, votingPower uint64) { + p.AddValidator(address, pubKey, votingPower) +} + +// RemoveValidator removes the given validator from the set. +// If the validator is not present in the set, the method errors out +func RemoveValidator(address string) { + p.RemoveValidator(address) +} + +// IsValidator returns a flag indicating if the given bech32 address +// is part of the validator set +func IsValidator(address string) bool { + return p.IsValidator(address) +} + +// GetValidators returns the JSON-encoded validator set +func GetValidators() string { + encodedSet, err := json.Marshal(p.GetValidators()) + if err != nil { + panic(ufmt.Sprintf("unable to marshal set, %s", err)) + } + + return string(encodedSet) +} From 8a6ea5ccd6b34ec5dff86c1b27e8a2866a969930 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 17 May 2024 12:52:18 +0200 Subject: [PATCH 02/71] Fix imports --- examples/gno.land/r/sys/vals/vals.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index a7a4e99ff8d..5a447b25c5e 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -3,8 +3,8 @@ package vals import ( "gno.land/p/demo/json" "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/pos" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/pos" + "gno.land/r/sys/vals/types" ) // Protocol defines the validator set protocol (PoA / PoS) From e53e6bfba868bcc8839603a7c20288f43eef8149 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 17 May 2024 17:13:02 +0200 Subject: [PATCH 03/71] Add gno.mods, add initial PoA --- examples/gno.land/r/sys/vals/gno.mod | 2 + examples/gno.land/r/sys/vals/poa/gno.mod | 6 + examples/gno.land/r/sys/vals/poa/option.gno | 19 ++ examples/gno.land/r/sys/vals/poa/poa.gno | 239 ++++++++++++++++++++ examples/gno.land/r/sys/vals/poa/vote.gno | 98 ++++++++ examples/gno.land/r/sys/vals/pos/gno.mod | 6 + examples/gno.land/r/sys/vals/pos/pos.gno | 22 +- examples/gno.land/r/sys/vals/types/gno.mod | 1 + examples/gno.land/r/sys/vals/vals.gno | 37 ++- 9 files changed, 412 insertions(+), 18 deletions(-) create mode 100644 examples/gno.land/r/sys/vals/poa/gno.mod create mode 100644 examples/gno.land/r/sys/vals/poa/option.gno create mode 100644 examples/gno.land/r/sys/vals/poa/poa.gno create mode 100644 examples/gno.land/r/sys/vals/poa/vote.gno create mode 100644 examples/gno.land/r/sys/vals/pos/gno.mod create mode 100644 examples/gno.land/r/sys/vals/types/gno.mod diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index 57c90c475af..227441ae7c2 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -3,4 +3,6 @@ module gno.land/r/sys/vals require ( gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/pos v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/poa/gno.mod b/examples/gno.land/r/sys/vals/poa/gno.mod new file mode 100644 index 00000000000..18f43c698a6 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poa/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals/poa + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/poa/option.gno b/examples/gno.land/r/sys/vals/poa/option.gno new file mode 100644 index 00000000000..ff4a71b5059 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poa/option.gno @@ -0,0 +1,19 @@ +package poa + +import "gno.land/r/sys/vals/types" + +type Option func(*PoA) + +// WithInitialSet sets the initial PoA validator set +func WithInitialSet(validators []types.Validator) Option { + return func(p *PoA) { + for index, validator := range validators { + p.validators = append(p.validators, validator) + p.addressToValidatorIndex[validator.Address] = index + + p.totalVotingPower += validator.VotingPower + } + + p.majorityPower = (2 * p.totalVotingPower) / 3 + } +} diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/r/sys/vals/poa/poa.gno new file mode 100644 index 00000000000..31de784b0c6 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poa/poa.gno @@ -0,0 +1,239 @@ +package poa + +import ( + "std" + + "gno.land/r/sys/vals/types" +) + +// voteWipeThreshold is the interval in which +// all active votes are wiped (proposal reset every X blocks) +const voteWipeThreshold = 500 // blocks + +const ( + UserVotedAddEvent = "UserVotedAdd" // emitted when someone votes to add a validator + UserVotedRemoveEvent = "UserVotedRemove" // emitted when someone votes to remove a validator +) + +// PoA specifies the Proof of Authority validator set. +// In order to become part of the set, users to be voted into +// the validator set by the majority of the existing set +type PoA struct { + // validators holds the current validator set. + // This slice can never practically grow more than ~150 elements, + // due to Tendermint's quadratic network complexity + validators []types.Validator + addressToValidatorIndex map[std.Address]int // address -> index + votes *votingSystem // system that keeps track of votes + + totalVotingPower uint64 // cached value for quick lookups + majorityPower uint64 // cached value for quick lookups +} + +// NewPoA creates a new empty Proof of Authority validator set +func NewPoA(opts ...Option) *PoA { + // Create the empty set + p := &PoA{ + validators: make([]types.Validator, 0), + addressToValidatorIndex: make(map[std.Address]int), + votes: newVotingSystem(), + } + + // Apply the options + for _, opt := range opts { + opt(p) + } + + return p +} + +// AddValidator adds a vote to add a new validator to the validator set. +// +// Criteria for being added: +// - caller is a user account +// - caller is a validator +// - the proposed address is not a validator +// - a 2/3+ majority of the set have voted to add +func (p *PoA) AddValidator(address std.Address, pubKey string, _ uint64) { + caller := std.GetOrigCaller() + + // Validate that the operation is a valid call + p.validateAdd(caller, address) + + // Attempt to wipe the votes, if needed + p.attemptVoteReset() + + // Cast the vote + p.votes.castVote(caller, address, true) + + // Emit the vote event + std.Emit( + UserVotedAddEvent, + "address", address.String(), + "voter", caller.String(), + ) + + // Calculate the votes + addCount, _ := p.votes.getTally(address) + + // Check if there is a majority + // to apply the action + if addCount <= p.majorityPower { + // No super majority to add + return + } + + // Execute the decision + v := types.Validator{ + Address: address, + PubKey: pubKey, // TODO: in the future, verify the public key + VotingPower: 1, // in this PoA system, everyone has the same voting power + } + + p.addValidator(v) +} + +// validateAdd validates a validator add call +func (p *PoA) validateAdd(caller, address std.Address) { + // Check if the caller is a user account + if !std.IsOriginCall() { + panic("caller not user account") + } + + // Check if the caller is in the set + if !p.IsValidator(caller) { + panic("validator already exists") + } + + // Check if the validator is already in the set + if p.IsValidator(address) { + panic("validator already exists") + } +} + +// addValidator adds the given validator to the PoA validator set +func (p *PoA) addValidator(v types.Validator) { + // Add the validator to the set + p.addressToValidatorIndex[v.Address] = len(p.validators) + p.validators = append(p.validators, v) + + // Update the total voting power + p.totalVotingPower += v.VotingPower + p.majorityPower = (2 * p.totalVotingPower) / 3 + + // Emit the validator set change + std.Emit( + types.ValidatorAddedEvent, + "address", v.Address.String(), + ) + + // Remove the candidate from the voting system + p.votes.resetCandidate(v.Address) +} + +// RemoveValidator adds a vote to remove a validator from the validator set. +// +// Criteria for being added: +// - caller is a user account +// - caller is a validator +// - the proposed address is a validator +// - a 2/3+ majority of the set have voted to remove +func (p *PoA) RemoveValidator(address std.Address) { + caller := std.GetOrigCaller() + + // Validate that the operation is a valid call + p.validateRemove(caller, address) + + // Attempt to wipe the votes, if needed + p.attemptVoteReset() + + // Cast the vote + p.votes.castVote(caller, address, false) + + // Emit the vote event + std.Emit( + UserVotedRemoveEvent, + "address", address.String(), + "voter", caller.String(), + ) + + // Calculate the votes + _, removeCount := p.votes.getTally(address) + + // Check if there is a majority + // to apply the action + if removeCount <= p.majorityPower { + // No super majority to remove + return + } + + // Execute the decision + p.removeValidator(address) +} + +// validateRemove validates a validator remove call +func (p *PoA) validateRemove(caller, address std.Address) { + // Check if the caller is a user account + if !std.IsOriginCall() { + panic("caller not user account") + } + + // Check if this request came from a validator + if !p.IsValidator(caller) { + panic("user is not validator") + } + + // Check if the address is a validator + if p.IsValidator(address) { + panic("address is not a validator") + } +} + +// removeValidator removes the given address from the PoA validator set +func (p *PoA) removeValidator(address std.Address) { + // Fetch the validator index + index := p.addressToValidatorIndex[address] + + // Remove the validator from the set + validator := p.validators[index] + p.validators = append(p.validators[:index], p.validators[index+1:]...) + + delete(p.addressToValidatorIndex, address) + + // Update the total voting power + p.totalVotingPower -= validator.VotingPower + p.majorityPower = (2 * p.totalVotingPower) / 3 + + // Emit the validator set change + std.Emit( + types.ValidatorRemovedEvent, + "address", address.String(), + ) + + // Remove the candidate from the voting system + p.votes.resetCandidate(address) +} + +// attemptVoteReset resets the votes if the wipe threshold +// has been reached +func (p *PoA) attemptVoteReset() { + if std.GetHeight()%voteWipeThreshold != 0 { + return + } + + // Wipe the current votes + p.votes.reset() +} + +// IsValidator returns a flag indicating if the address +// is part of the staked validator set +func (p *PoA) IsValidator(address std.Address) bool { + _, exists := p.addressToValidatorIndex[address] + + return exists +} + +// GetValidators returns the current staked validator set +func (p *PoA) GetValidators() []types.Validator { + return p.validators +} diff --git a/examples/gno.land/r/sys/vals/poa/vote.gno b/examples/gno.land/r/sys/vals/poa/vote.gno new file mode 100644 index 00000000000..e642b2375b4 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poa/vote.gno @@ -0,0 +1,98 @@ +package poa + +import ( + "std" + "strings" + + "gno.land/p/demo/ufmt" +) + +type ( + tallyMap map[std.Address]tally + haveVotedMap map[string]struct{} +) + +type tally struct { + toAdd uint64 + toRemove uint64 +} + +type votingSystem struct { + tallyMap tallyMap + haveVotedMap haveVotedMap +} + +// newVotingSystem creates a new PoA voting system +func newVotingSystem() *votingSystem { + return &votingSystem{ + tallyMap: make(tallyMap), + haveVotedMap: make(haveVotedMap), + } +} + +// castVote casts a new vote to the given candidate +func (v *votingSystem) castVote(voter, candidate std.Address, toAdd bool) { + // Construct a unique proposal key (voterAddr_candidateAddr) + votedKey := ufmt.Sprintf( + "%s_%s", + voter.String(), + candidate.String(), + ) + + if _, voted := v.haveVotedMap[votedKey]; voted { + // Vote already cast, noop + return + } + + // Fetch the tally + t, exists := v.tallyMap[candidate] + if !exists { + t = tally{ + toAdd: 0, + toRemove: 0, + } + } + + // Update the tally + if toAdd { + t.toAdd++ + } else { + t.toRemove++ + } + + // Save the tally + v.tallyMap[candidate] = t + v.haveVotedMap[votedKey] = struct{}{} +} + +// getTally returns the current voting tally for a candidate +func (v *votingSystem) getTally(candidate std.Address) (uint64, uint64) { + t, exists := v.tallyMap[candidate] + if !exists { + return 0, 0 + } + + return t.toAdd, t.toRemove +} + +// reset removes all candidates from the voting system +func (v *votingSystem) reset() { + v.tallyMap = make(tallyMap) + v.haveVotedMap = make(haveVotedMap) +} + +// resetCandidate removes a candidate from the voting system +func (v *votingSystem) resetCandidate(candidate std.Address) { + // Drop the tally entries + delete(v.tallyMap, candidate) + + // Drop the tracked votes + candidateStr := candidate.String() + for key := range v.haveVotedMap { + if !strings.Contains(key, candidateStr) { + continue + } + + delete(v.haveVotedMap, key) + } +} diff --git a/examples/gno.land/r/sys/vals/pos/gno.mod b/examples/gno.land/r/sys/vals/pos/gno.mod new file mode 100644 index 00000000000..3270ff929ec --- /dev/null +++ b/examples/gno.land/r/sys/vals/pos/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals/pos + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index 6b4c1ca3ba3..101f4b9e783 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -55,7 +55,7 @@ func NewPoS(opts ...Option) *PoS { // - caller is not already a validator // - caller has staked funds >= the current staking threshold // - the validator set is less than the maximum size -func (p *PoS) AddValidator(address, pubKey string, votingPower uint64) { +func (p *PoS) AddValidator(address std.Address, pubKey string, votingPower uint64) { // Check if the caller is a user account if !std.IsOriginCall() { panic("caller not user account") @@ -94,8 +94,8 @@ func (p *PoS) AddValidator(address, pubKey string, votingPower uint64) { // Emit the event that the user staked funds std.Emit( UserStakedEvent, - "address", address, - "stake", ufmt.Sprintf("%d", stakedAmount.Amount), + "address", address.String(), + "stake", ufmt.Sprintf("%d", addressStake.Amount), ) // Check if the caller can become a validator @@ -116,7 +116,7 @@ func (p *PoS) AddValidator(address, pubKey string, votingPower uint64) { // Emit the validator set change std.Emit( types.ValidatorAddedEvent, - "address", address, + "address", address.String(), ) } @@ -127,7 +127,7 @@ func (p *PoS) AddValidator(address, pubKey string, votingPower uint64) { func (p *PoS) canBecomeValidator(address std.Address) bool { stake, _ := p.addressToStakedAmount[address] - return !p.IsValidator(address.String()) && stake.IsAllGTE(stakeThreshold) + return !p.IsValidator(address) && stake.IsGTE(stakeThreshold) } // RemoveValidator removes a validator from the validator set. @@ -137,7 +137,7 @@ func (p *PoS) canBecomeValidator(address std.Address) bool { // - caller is a user account // - caller is a validator // - the validator set is more than the minimum size -func (p *PoS) RemoveValidator(address string) { +func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is a user account if !std.IsOriginCall() { panic("caller not user account") @@ -164,7 +164,7 @@ func (p *PoS) RemoveValidator(address string) { } // Check if the validator is in the set - index := addressToValidatorIndex[caller] + index := p.addressToValidatorIndex[caller] // Remove the validator from the set p.validators = append(p.validators[:index], p.validators[index+1:]...) @@ -178,13 +178,13 @@ func (p *PoS) RemoveValidator(address string) { // Emit the validator set change std.Emit( types.ValidatorRemovedEvent, - "address", address, + "address", address.String(), ) // Emit the unstake event std.Emit( UserUnstakedEvent, - "address", address, + "address", address.String(), "stake", ufmt.Sprintf("%d", addressStake.Amount), ) } @@ -192,7 +192,7 @@ func (p *PoS) RemoveValidator(address string) { // returnStake returns the specified stake to the given address func returnStake(address std.Address, amount std.Coin) { // Derive the current package address - from := std.DerivePkgAddr(std.CurrentRealm()) + from := std.DerivePkgAddr(std.CurrentRealm().Addr().String()) // Fetch the banker banker := std.GetBanker(std.BankerTypeRealmSend) @@ -203,7 +203,7 @@ func returnStake(address std.Address, amount std.Coin) { // IsValidator returns a flag indicating if the address // is part of the staked validator set -func (p *PoS) IsValidator(address string) bool { +func (p *PoS) IsValidator(address std.Address) bool { _, exists := p.addressToValidatorIndex[std.Address(address)] return exists diff --git a/examples/gno.land/r/sys/vals/types/gno.mod b/examples/gno.land/r/sys/vals/types/gno.mod new file mode 100644 index 00000000000..32dd9f06413 --- /dev/null +++ b/examples/gno.land/r/sys/vals/types/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sys/vals/types diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 5a447b25c5e..3686636aa23 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -1,6 +1,8 @@ package vals import ( + "std" + "gno.land/p/demo/json" "gno.land/p/demo/ufmt" "gno.land/r/sys/vals/pos" @@ -11,15 +13,19 @@ import ( type Protocol interface { // AddValidator adds a new validator to the validator set. // If the validator is already present, the method should error out - AddValidator(address, pubKey string, votingPower uint64) + // + // TODO: This API is not ideal -- the address should be derived from + // the public key, and not be passed in as such, but currently Gno + // does not support crypto address derivation + AddValidator(address std.Address, pubKey string, votingPower uint64) // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method should error out - RemoveValidator(address string) + RemoveValidator(address std.Address) // IsValidator returns a flag indicating if the given // bech32 address is part of the validator set - IsValidator(address string) bool + IsValidator(address std.Address) bool // GetValidators returns the JSON-encoded validator set. // The reason for returning JSON, and not a `[]Validator`, @@ -34,27 +40,44 @@ var p Protocol = pos.NewPoS() // AddValidator adds a new validator to the validator set. // If the validator is already present, the method errors out func AddValidator(address, pubKey string, votingPower uint64) { - p.AddValidator(address, pubKey, votingPower) + p.AddValidator(std.Address(address), pubKey, votingPower) } // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method errors out func RemoveValidator(address string) { - p.RemoveValidator(address) + p.RemoveValidator(std.Address(address)) } // IsValidator returns a flag indicating if the given bech32 address // is part of the validator set func IsValidator(address string) bool { - return p.IsValidator(address) + return p.IsValidator(std.Address(address)) } // GetValidators returns the JSON-encoded validator set func GetValidators() string { - encodedSet, err := json.Marshal(p.GetValidators()) + encodedSet, err := json.Marshal(prepareForJSON(p.GetValidators())) if err != nil { panic(ufmt.Sprintf("unable to marshal set, %s", err)) } return string(encodedSet) } + +// prepareForJSON prepares the validator set for JSON encoding +func prepareForJSON(vals []types.Validator) *json.Node { + nodes := make([]*json.Node, 0, len(vals)) + + for _, v := range vals { + node := json.ObjectNode("", map[string]*json.Node{ + "address": json.StringNode("address", v.Address.String()), + "pub_key": json.StringNode("pub_key", v.PubKey), + "voting_power": json.NumberNode("voting_power", float64(v.VotingPower)), + }) + + nodes = append(nodes, node) + } + + return json.ArrayNode("", nodes) +} From 9a8bc1d2b26a70bca8cb55fa9238a47989d0e0ae Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 17 May 2024 17:26:52 +0200 Subject: [PATCH 04/71] Simplify API --- examples/gno.land/r/sys/vals/poa/option.gno | 2 +- examples/gno.land/r/sys/vals/poa/poa.gno | 12 ++++----- examples/gno.land/r/sys/vals/pos/option.gno | 2 +- examples/gno.land/r/sys/vals/pos/pos.gno | 27 +++++++++++++++------ examples/gno.land/r/sys/vals/vals.gno | 10 ++++---- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/examples/gno.land/r/sys/vals/poa/option.gno b/examples/gno.land/r/sys/vals/poa/option.gno index ff4a71b5059..8be3d115c48 100644 --- a/examples/gno.land/r/sys/vals/poa/option.gno +++ b/examples/gno.land/r/sys/vals/poa/option.gno @@ -5,7 +5,7 @@ import "gno.land/r/sys/vals/types" type Option func(*PoA) // WithInitialSet sets the initial PoA validator set -func WithInitialSet(validators []types.Validator) Option { +func WithInitialSet(validators []*types.Validator) Option { return func(p *PoA) { for index, validator := range validators { p.validators = append(p.validators, validator) diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/r/sys/vals/poa/poa.gno index 31de784b0c6..7e4087d2f9e 100644 --- a/examples/gno.land/r/sys/vals/poa/poa.gno +++ b/examples/gno.land/r/sys/vals/poa/poa.gno @@ -22,7 +22,7 @@ type PoA struct { // validators holds the current validator set. // This slice can never practically grow more than ~150 elements, // due to Tendermint's quadratic network complexity - validators []types.Validator + validators []*types.Validator addressToValidatorIndex map[std.Address]int // address -> index votes *votingSystem // system that keeps track of votes @@ -34,7 +34,7 @@ type PoA struct { func NewPoA(opts ...Option) *PoA { // Create the empty set p := &PoA{ - validators: make([]types.Validator, 0), + validators: make([]*types.Validator, 0), addressToValidatorIndex: make(map[std.Address]int), votes: newVotingSystem(), } @@ -54,7 +54,7 @@ func NewPoA(opts ...Option) *PoA { // - caller is a validator // - the proposed address is not a validator // - a 2/3+ majority of the set have voted to add -func (p *PoA) AddValidator(address std.Address, pubKey string, _ uint64) { +func (p *PoA) AddValidator(address std.Address, pubKey string) { caller := std.GetOrigCaller() // Validate that the operation is a valid call @@ -84,7 +84,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string, _ uint64) { } // Execute the decision - v := types.Validator{ + v := &types.Validator{ Address: address, PubKey: pubKey, // TODO: in the future, verify the public key VotingPower: 1, // in this PoA system, everyone has the same voting power @@ -112,7 +112,7 @@ func (p *PoA) validateAdd(caller, address std.Address) { } // addValidator adds the given validator to the PoA validator set -func (p *PoA) addValidator(v types.Validator) { +func (p *PoA) addValidator(v *types.Validator) { // Add the validator to the set p.addressToValidatorIndex[v.Address] = len(p.validators) p.validators = append(p.validators, v) @@ -234,6 +234,6 @@ func (p *PoA) IsValidator(address std.Address) bool { } // GetValidators returns the current staked validator set -func (p *PoA) GetValidators() []types.Validator { +func (p *PoA) GetValidators() []*types.Validator { return p.validators } diff --git a/examples/gno.land/r/sys/vals/pos/option.gno b/examples/gno.land/r/sys/vals/pos/option.gno index 8bf1b460d18..79602bac232 100644 --- a/examples/gno.land/r/sys/vals/pos/option.gno +++ b/examples/gno.land/r/sys/vals/pos/option.gno @@ -9,7 +9,7 @@ import ( type Option func(*PoS) // WithInitialSet sets the initial PoS validator set, with empty stakes -func WithInitialSet(validators []types.Validator) Option { +func WithInitialSet(validators []*types.Validator) Option { return func(p *PoS) { for index, validator := range validators { p.validators = append(p.validators, validator) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index 101f4b9e783..a2e027dbb50 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -26,16 +26,18 @@ type PoS struct { // validators holds the current validator set. // This slice can never practically grow more than ~150 elements, // due to Tendermint's quadratic network complexity - validators []types.Validator + validators []*types.Validator addressToValidatorIndex map[std.Address]int // address -> index addressToStakedAmount map[std.Address]std.Coin // address -> staked amount + + totalStake uint64 // the total staked amount } // NewPoS creates a new empty Proof of Stake validator set func NewPoS(opts ...Option) *PoS { // Create the empty set p := &PoS{ - validators: make([]types.Validator, 0), + validators: make([]*types.Validator, 0), addressToValidatorIndex: make(map[std.Address]int), addressToStakedAmount: make(map[std.Address]std.Coin), } @@ -55,7 +57,7 @@ func NewPoS(opts ...Option) *PoS { // - caller is not already a validator // - caller has staked funds >= the current staking threshold // - the validator set is less than the maximum size -func (p *PoS) AddValidator(address std.Address, pubKey string, votingPower uint64) { +func (p *PoS) AddValidator(address std.Address, pubKey string) { // Check if the caller is a user account if !std.IsOriginCall() { panic("caller not user account") @@ -77,7 +79,7 @@ func (p *PoS) AddValidator(address std.Address, pubKey string, votingPower uint6 ) // Check if the caller is supplying their own address - if address != caller.String() { + if address != caller { panic("validator address is not origin") } @@ -90,6 +92,7 @@ func (p *PoS) AddValidator(address std.Address, pubKey string, votingPower uint6 // Save the staked amount addressStake.Amount += stakedAmount p.addressToStakedAmount[caller] = addressStake + p.totalStake += uint64(stakedAmount) // Emit the event that the user staked funds std.Emit( @@ -104,10 +107,10 @@ func (p *PoS) AddValidator(address std.Address, pubKey string, votingPower uint6 } // Add the caller to the validator set - v := types.Validator{ + v := &types.Validator{ Address: caller, - PubKey: pubKey, // TODO check public key? - VotingPower: votingPower, // TODO validate voting power? + PubKey: pubKey, + VotingPower: uint64(addressStake.Amount) / p.totalStake, // voting power is proportional to the stake } p.addressToValidatorIndex[caller] = len(p.validators) @@ -120,6 +123,14 @@ func (p *PoS) AddValidator(address std.Address, pubKey string, votingPower uint6 ) } +// rebalanceVotingPower rebalances the voting power of the validator set +func (p *PoS) rebalanceVotingPower() { + for _, v := range p.validators { + stakedAmount := p.addressToStakedAmount[v.Address] + v.VotingPower = uint64(stakedAmount.Amount) / p.totalStake + } +} + // canBecomeValidator checks if the address fulfills all criteria for // becoming a validator: // - is not already a validator @@ -210,6 +221,6 @@ func (p *PoS) IsValidator(address std.Address) bool { } // GetValidators returns the current staked validator set -func (p *PoS) GetValidators() []types.Validator { +func (p *PoS) GetValidators() []*types.Validator { return p.validators } diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 3686636aa23..07aa8d9e9cc 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -17,7 +17,7 @@ type Protocol interface { // TODO: This API is not ideal -- the address should be derived from // the public key, and not be passed in as such, but currently Gno // does not support crypto address derivation - AddValidator(address std.Address, pubKey string, votingPower uint64) + AddValidator(address std.Address, pubKey string) // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method should error out @@ -31,7 +31,7 @@ type Protocol interface { // The reason for returning JSON, and not a `[]Validator`, // is because Gno does not yet support an ABI-like functionality // for clients interacting with the Realm / Package methods - GetValidators() []types.Validator + GetValidators() []*types.Validator } // p is the underlying validator set protocol (PoA / PoS) @@ -39,8 +39,8 @@ var p Protocol = pos.NewPoS() // AddValidator adds a new validator to the validator set. // If the validator is already present, the method errors out -func AddValidator(address, pubKey string, votingPower uint64) { - p.AddValidator(std.Address(address), pubKey, votingPower) +func AddValidator(address, pubKey string) { + p.AddValidator(std.Address(address), pubKey) } // RemoveValidator removes the given validator from the set. @@ -66,7 +66,7 @@ func GetValidators() string { } // prepareForJSON prepares the validator set for JSON encoding -func prepareForJSON(vals []types.Validator) *json.Node { +func prepareForJSON(vals []*types.Validator) *json.Node { nodes := make([]*json.Node, 0, len(vals)) for _, v := range vals { From b4a24521f4d84759a8b5ed230448388f572de1ea Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 21 May 2024 17:32:38 +0200 Subject: [PATCH 05/71] Add initial PoS add validator tests --- examples/gno.land/r/sys/vals/pos/pos.gno | 19 +- examples/gno.land/r/sys/vals/pos/pos_test.gno | 171 ++++++++++++++++++ gnovm/stdlibs/testing/testing.gno | 70 ++++++- 3 files changed, 249 insertions(+), 11 deletions(-) create mode 100644 examples/gno.land/r/sys/vals/pos/pos_test.gno diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index a2e027dbb50..16263804d5d 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -9,6 +9,13 @@ import ( var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs +const ( + errCallerNotUserAccount = "caller not user account" + errValidatorExists = "validator already exists" + errMaximumValidatorCount = "maximum validator count reached" + errValidatorNotOrigin = "validator address is not origin" +) + const ( minValidatorCount = 4 // the absolute minimum number of validators in the set maxValidatorCount = 150 // the practical maximum number of validators in the set @@ -59,18 +66,18 @@ func NewPoS(opts ...Option) *PoS { // - the validator set is less than the maximum size func (p *PoS) AddValidator(address std.Address, pubKey string) { // Check if the caller is a user account - if !std.IsOriginCall() { - panic("caller not user account") + if !std.PrevRealm().IsUser() { + panic(errCallerNotUserAccount) } // Check if the validator is already in the set if p.IsValidator(address) { - panic("validator already exists") + panic(errValidatorExists) } // Check if the limit is reached - if len(p.validators) <= maxValidatorCount { - panic("maximum validator count reached") + if len(p.validators) == maxValidatorCount { + panic(errMaximumValidatorCount) } var ( @@ -80,7 +87,7 @@ func (p *PoS) AddValidator(address std.Address, pubKey string) { // Check if the caller is supplying their own address if address != caller { - panic("validator address is not origin") + panic(errValidatorNotOrigin) } // Fetch the already staked amount diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno new file mode 100644 index 00000000000..58e7f983612 --- /dev/null +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -0,0 +1,171 @@ +package pos + +import ( + "std" + "testing" + + "gno.land/p/demo/ufmt" + "gno.land/r/sys/vals/types" +) + +func generateTestValidators(count int) []*types.Validator { + vals := make([]*types.Validator, 0, count) + + for i := 0; i < count; i++ { + val := &types.Validator{ + Address: std.Address(ufmt.Sprintf("%d", i)), + PubKey: "public-key", + VotingPower: 0, + } + + vals = append(vals, val) + } + + return vals +} + +func TestPoS_AddValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("validator already in set", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = callerAddress + initialSet[0].PubKey = callerKey + + // Create the protocol with an initial set + p := NewPoS(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to add the validator + testing.PanicsWithError(t, errValidatorExists, func() { + p.AddValidator(callerAddress, callerKey) + }) + }) + + t.Run("validator set full", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(maxValidatorCount) + ) + + // Create the protocol with an initial set + p := NewPoS(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to add the validator + testing.PanicsWithError(t, errMaximumValidatorCount, func() { + p.AddValidator(callerAddress, callerKey) + }) + }) + + t.Run("validator address is not the origin", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(10) + ) + + // Create the protocol with an initial set + p := NewPoS(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(std.Address("random address")) + + // Attempt to add the validator + testing.PanicsWithError(t, errValidatorNotOrigin, func() { + p.AddValidator(callerAddress, callerKey) + }) + }) +} + +func TestPoS_AddValidator(t *testing.T) { + t.Parallel() + + t.Run("user becomes validator in a few tries", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + numTries = int64(10) + + value = std.Coins{{"ugnot", stakeThreshold.Amount / numTries}} + ) + + // Create the protocol with an initial set + p := NewPoS() + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + std.TestSetOrigSend(value, std.Coins{}) + + for i := int64(0); i < numTries; i++ { + if p.IsValidator(callerAddress) { + t.Fatalf("should not have a validator at try %d", i) + } + + // Attempt to add the validator + testing.NotPanics(t, func() { + p.AddValidator(callerAddress, callerKey) + }) + } + + // Make sure the user became a validator + if !p.IsValidator(callerAddress) { + t.Fatalf("should be a validator") + } + }) + + t.Run("user becomes validator in single stake (threshold)", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + value = std.Coins{{"ugnot", stakeThreshold.Amount}} + ) + + // Create the protocol with an initial set + p := NewPoS() + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + std.TestSetOrigSend(value, std.Coins{}) + + // Make sure the caller is not a validator + if p.IsValidator(callerAddress) { + t.Fatalf("should not be a validator") + } + + // Attempt to add the validator + testing.NotPanics(t, func() { + p.AddValidator(callerAddress, callerKey) + }) + + // Make sure the user became a validator + if !p.IsValidator(callerAddress) { + t.Fatalf("should be a validator") + } + }) +} diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index fb13c0f39cd..a5ae7be05ad 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -5,12 +5,11 @@ import ( "encoding/json" "fmt" "os" - "regexp" "strconv" "strings" ) -//---------------------------------------- +// ---------------------------------------- // Top level functions // skipErr is the type of the panic created by SkipNow @@ -40,6 +39,67 @@ func Recover(result Setter) { panic(r) } +func extractPanicErr(r interface{}) string { + err, ok := r.(error) + if ok { + return err.Error() + } + + errStr, ok := r.(string) + if ok { + return errStr + } + + return "unknown error" +} + +// PanicsWithError asserts that the code inside the specified func panics, +// and that the recovered panic value is an error that satisfies the given message +func PanicsWithError(t *T, errString string, f func()) bool { + t.Helper() + + var valid bool + + defer func() { + if r := recover(); r != nil { + // Check if the error matches + panicErr := extractPanicErr(r) + if panicErr == errString { + valid = true + + return + } + + t.Fatalf("Function panicked with err, %s", panicErr) + } + }() + + // Run the callback + f() + + return valid +} + +// NotPanics asserts that the code inside the specified func does NOT panic +func NotPanics(t *T, f func()) bool { + t.Helper() + + valid := true + + defer func() { + if r := recover(); r != nil { + valid = false + + t.Fatalf("Function panicked with err, %s", extractPanicErr(r)) + } + }() + + // Run the callback + f() + + return valid +} + type Setter interface { Set(v interface{}) } @@ -61,7 +121,7 @@ func AllocsPerRun2(runs int, f func()) (total int) { return 0 } -//---------------------------------------- +// ---------------------------------------- // T type T struct { @@ -226,7 +286,7 @@ func (t *T) report() Report { } } -//---------------------------------------- +// ---------------------------------------- // B // TODO: actually implement @@ -262,7 +322,7 @@ func (b *B) StartTimer() { panic("not yet implemen func (b *B) StopTimer() { panic("not yet implemented") } func (b *B) TempDir() string { panic("not yet implemented") } -//---------------------------------------- +// ---------------------------------------- // PB // TODO: actually implement From 1c6a775e94136a2e8ef5e07559bf60bf386ce726 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 21 May 2024 17:40:00 +0200 Subject: [PATCH 06/71] Add additional PoS tests --- examples/gno.land/r/sys/vals/pos/pos_test.gno | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno index 58e7f983612..486dfe53e31 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -8,6 +8,7 @@ import ( "gno.land/r/sys/vals/types" ) +// generateTestValidators generates a dummy validator set func generateTestValidators(count int) []*types.Validator { vals := make([]*types.Validator, 0, count) @@ -169,3 +170,79 @@ func TestPoS_AddValidator(t *testing.T) { } }) } + +func TestPoS_IsValidator(t *testing.T) { + t.Parallel() + + t.Run("address is not in the set", func(t *testing.T) { + t.Parallel() + + // Empty PoS set + p := NewPoS() + + if p.IsValidator(std.Address("random address")) { + t.Fatal("should not be a validator") + } + }) + + t.Run("address is in the set", func(t *testing.T) { + t.Parallel() + + initialSet := generateTestValidators(10) + + // Existing PoS set + p := NewPoS(WithInitialSet(initialSet)) + + if !p.IsValidator(initialSet[0].Address) { + t.Fatal("should be a validator") + } + }) +} + +func TestPoS_GetValidators(t *testing.T) { + t.Parallel() + + t.Run("empty set", func(t *testing.T) { + t.Parallel() + + // Empty PoS set + p := NewPoS() + + if len(p.GetValidators()) != 0 { + t.Fatal("should be empty set") + } + }) + + t.Run("existing set", func(t *testing.T) { + t.Parallel() + + initialSet := generateTestValidators(10) + + // Existing PoS set + p := NewPoS(WithInitialSet(initialSet)) + + vals := p.GetValidators() + + // Make sure the set has a valid length + if len(vals) != len(initialSet) { + t.Fatal("invalid set length") + } + + // Make sure each validator is valid + for i, val := range vals { + ref := initialSet[i] + + if val.Address != ref.Address { + t.Fatal("address mismatch") + } + + if val.PubKey != ref.PubKey { + t.Fatal("address mismatch") + } + + if val.VotingPower != 0 { + t.Fatal("invalid voting power") + } + } + }) +} From 54884fda1bff93485d65cafd7bd391829749f40b Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 21 May 2024 17:52:02 +0200 Subject: [PATCH 07/71] Add initial PoS remove validator tests --- examples/gno.land/r/sys/vals/pos/pos.gno | 14 ++-- examples/gno.land/r/sys/vals/pos/pos_test.gno | 68 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index 16263804d5d..03473d9c426 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -12,7 +12,9 @@ var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs const ( errCallerNotUserAccount = "caller not user account" errValidatorExists = "validator already exists" + errValidatorMissing = "validator doesn't exists" errMaximumValidatorCount = "maximum validator count reached" + errMinimumValidatorCount = "minimum validator count reached" errValidatorNotOrigin = "validator address is not origin" ) @@ -157,18 +159,18 @@ func (p *PoS) canBecomeValidator(address std.Address) bool { // - the validator set is more than the minimum size func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is a user account - if !std.IsOriginCall() { - panic("caller not user account") + if !std.PrevRealm().IsUser() { + panic(errCallerNotUserAccount) } // Check if this request came from a validator - if !p.IsValidator(std.GetOrigCaller()) { - panic("user is not validator") + if !p.IsValidator(address) { + panic(errValidatorMissing) } // Check if the limit is reached if len(p.validators) == minValidatorCount { - panic("minimum validator count reached") + panic(errMinimumValidatorCount) } var ( @@ -178,7 +180,7 @@ func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is supplying their own address if address != caller.String() { - panic("validator address is not origin") + panic(errValidatorNotOrigin) } // Check if the validator is in the set diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno index 486dfe53e31..2633e7ecd65 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -246,3 +246,71 @@ func TestPoS_GetValidators(t *testing.T) { } }) } + +func TestPoS_RemoveValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("validator not in set", func(t *testing.T) { + t.Parallel() + + callerAddress := std.Address("caller") + + // Create the protocol with an initial set + p := NewPoS() + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to remove the validator + testing.PanicsWithError(t, errValidatorMissing, func() { + p.RemoveValidator(callerAddress) + }) + }) + + t.Run("validator set at minimum", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(minValidatorCount) + ) + + initialSet[0].Address = callerAddress + initialSet[0].PubKey = callerKey + + // Create the protocol with an initial set + p := NewPoS(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to remove the validator + testing.PanicsWithError(t, errMinimumValidatorCount, func() { + p.RemoveValidator(callerAddress) + }) + }) + + t.Run("validator address is not the origin", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(10) + ) + + // Create the protocol with an initial set + p := NewPoS(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to remove the validator + testing.PanicsWithError(t, errValidatorNotOrigin, func() { + p.RemoveValidator(initialSet[0].Address) + }) + }) +} From d2e1e243c711ec10512b2119496bd3730a3e4780 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 24 May 2024 14:48:08 +0200 Subject: [PATCH 08/71] Add unit tests for PoS validator removal --- examples/gno.land/r/sys/vals/pos/pos.gno | 4 +- examples/gno.land/r/sys/vals/pos/pos_test.gno | 59 ++++++++++++++++++- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index 03473d9c426..b3c91395625 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -211,8 +211,8 @@ func (p *PoS) RemoveValidator(address std.Address) { // returnStake returns the specified stake to the given address func returnStake(address std.Address, amount std.Coin) { - // Derive the current package address - from := std.DerivePkgAddr(std.CurrentRealm().Addr().String()) + // Get the current package address + from := std.CurrentRealm().Addr() // Fetch the banker banker := std.GetBanker(std.BankerTypeRealmSend) diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno index 2633e7ecd65..8ee2ead41c2 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -41,7 +41,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { initialSet[0].Address = callerAddress initialSet[0].PubKey = callerKey - // Create the protocol with an initial set + // Create the protocol with no initial set p := NewPoS(WithInitialSet(initialSet)) // Set the origin caller @@ -113,7 +113,7 @@ func TestPoS_AddValidator(t *testing.T) { value = std.Coins{{"ugnot", stakeThreshold.Amount / numTries}} ) - // Create the protocol with an initial set + // Create the protocol with no initial set p := NewPoS() // Set the origin caller @@ -255,7 +255,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { callerAddress := std.Address("caller") - // Create the protocol with an initial set + // Create the protocol with no initial set p := NewPoS() // Set the origin caller @@ -314,3 +314,56 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { }) }) } + +func TestPoS_RemoveValidator(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + value = std.Coins{{"ugnot", stakeThreshold.Amount}} + ) + + // Create an initial PoS protocol with no set + p := NewPoS() + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + std.TestSetOrigSend(value, std.Coins{}) + + // Make sure the caller is not a validator + if p.IsValidator(callerAddress) { + t.Fatalf("should not be a validator") + } + + // Attempt to add the validator + testing.NotPanics(t, func() { + p.AddValidator(callerAddress, callerKey) + }) + + // Make sure the user became a validator + if !p.IsValidator(callerAddress) { + t.Fatalf("should be a validator") + } + + // Attempt to remove the validator + testing.NotPanics(t, func() { + p.RemoveValidator(callerAddress) + }) + + // Make sure the validator is removed + if p.IsValidator(callerAddress) { + t.Fatalf("should not be a validator") + } + + // Make sure the balance is updated + var ( + banker = std.GetBanker(std.BankerTypeReadonly) + balance = banker.GetCoins(callerAddress).AmountOf("ugnot") + ) + + if balance != stakeThreshold.Amount { + t.Fatalf("returned balance mismatch") + } +} From 08f1351fb676b89b8fe588c07d959021b76b9cb6 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 24 May 2024 16:47:41 +0200 Subject: [PATCH 09/71] Add unit tests for PoA validator operations --- examples/gno.land/r/sys/vals/poa/poa.gno | 37 +-- examples/gno.land/r/sys/vals/poa/poa_test.gno | 310 ++++++++++++++++++ examples/gno.land/r/sys/vals/poa/vote.gno | 12 +- examples/gno.land/r/sys/vals/pos/pos.gno | 66 ++-- examples/gno.land/r/sys/vals/pos/pos_test.gno | 8 +- examples/gno.land/r/sys/vals/types/errors.gno | 8 + 6 files changed, 371 insertions(+), 70 deletions(-) create mode 100644 examples/gno.land/r/sys/vals/poa/poa_test.gno create mode 100644 examples/gno.land/r/sys/vals/types/errors.gno diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/r/sys/vals/poa/poa.gno index 7e4087d2f9e..817e2f177ee 100644 --- a/examples/gno.land/r/sys/vals/poa/poa.gno +++ b/examples/gno.land/r/sys/vals/poa/poa.gno @@ -15,6 +15,8 @@ const ( UserVotedRemoveEvent = "UserVotedRemove" // emitted when someone votes to remove a validator ) +var errCallerNotValidator = "caller is not validator" + // PoA specifies the Proof of Authority validator set. // In order to become part of the set, users to be voted into // the validator set by the majority of the existing set @@ -60,9 +62,6 @@ func (p *PoA) AddValidator(address std.Address, pubKey string) { // Validate that the operation is a valid call p.validateAdd(caller, address) - // Attempt to wipe the votes, if needed - p.attemptVoteReset() - // Cast the vote p.votes.castVote(caller, address, true) @@ -96,18 +95,18 @@ func (p *PoA) AddValidator(address std.Address, pubKey string) { // validateAdd validates a validator add call func (p *PoA) validateAdd(caller, address std.Address) { // Check if the caller is a user account - if !std.IsOriginCall() { - panic("caller not user account") + if !std.PrevRealm().IsUser() { + panic(types.ErrCallerNotUserAccount) } // Check if the caller is in the set if !p.IsValidator(caller) { - panic("validator already exists") + panic(types.ErrValidatorMissing) } // Check if the validator is already in the set if p.IsValidator(address) { - panic("validator already exists") + panic(types.ErrValidatorExists) } } @@ -144,9 +143,6 @@ func (p *PoA) RemoveValidator(address std.Address) { // Validate that the operation is a valid call p.validateRemove(caller, address) - // Attempt to wipe the votes, if needed - p.attemptVoteReset() - // Cast the vote p.votes.castVote(caller, address, false) @@ -174,18 +170,18 @@ func (p *PoA) RemoveValidator(address std.Address) { // validateRemove validates a validator remove call func (p *PoA) validateRemove(caller, address std.Address) { // Check if the caller is a user account - if !std.IsOriginCall() { - panic("caller not user account") + if !std.PrevRealm().IsUser() { + panic(types.ErrCallerNotUserAccount) } // Check if this request came from a validator if !p.IsValidator(caller) { - panic("user is not validator") + panic(errCallerNotValidator) } // Check if the address is a validator - if p.IsValidator(address) { - panic("address is not a validator") + if !p.IsValidator(address) { + panic(types.ErrValidatorMissing) } } @@ -214,17 +210,6 @@ func (p *PoA) removeValidator(address std.Address) { p.votes.resetCandidate(address) } -// attemptVoteReset resets the votes if the wipe threshold -// has been reached -func (p *PoA) attemptVoteReset() { - if std.GetHeight()%voteWipeThreshold != 0 { - return - } - - // Wipe the current votes - p.votes.reset() -} - // IsValidator returns a flag indicating if the address // is part of the staked validator set func (p *PoA) IsValidator(address std.Address) bool { diff --git a/examples/gno.land/r/sys/vals/poa/poa_test.gno b/examples/gno.land/r/sys/vals/poa/poa_test.gno new file mode 100644 index 00000000000..282ecb7a845 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poa/poa_test.gno @@ -0,0 +1,310 @@ +package poa + +import ( + "std" + "testing" + + "gno.land/p/demo/ufmt" + "gno.land/r/sys/vals/types" +) + +// generateTestValidators generates a dummy validator set +func generateTestValidators(count int) []*types.Validator { + vals := make([]*types.Validator, 0, count) + + for i := 0; i < count; i++ { + val := &types.Validator{ + Address: std.Address(ufmt.Sprintf("%d", i)), + PubKey: "public-key", + VotingPower: 0, + } + + vals = append(vals, val) + } + + return vals +} + +func TestPoA_AddValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("validator already in set", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = callerAddress + initialSet[0].PubKey = callerKey + + // Create the protocol with no initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to add the validator + testing.PanicsWithError(t, types.ErrValidatorExists, func() { + p.AddValidator(callerAddress, callerKey) + }) + }) + + t.Run("caller not validator", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + ) + + // Create the protocol with no initial set + p := NewPoA() + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to add the validator + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + p.AddValidator(callerAddress, callerKey) + }) + }) +} + +func TestPoA_AddValidator(t *testing.T) { + t.Parallel() + + t.Run("validator voted in by single node", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + proposedAddress = std.Address("proposal") + proposedKey = "proposed-key" + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = callerAddress + initialSet[0].PubKey = callerKey + initialSet[0].VotingPower = 1 + + // Create the protocol with no initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to add the validator + testing.NotPanics(t, func() { + p.AddValidator(proposedAddress, proposedKey) + }) + + // Make sure the validator is added + if !p.IsValidator(proposedAddress) { + t.Fatal("address is not validator") + } + + // Make sure the total voting power is changed + if p.totalVotingPower != 2 { + t.Fatal("invalid total voting power") + } + + // Make sure the majority voting power is changed + if p.majorityPower != 1 { // 50% is the 2/3 majority for 2 nodes + t.Fatal("invalid majority voting power") + } + }) + + t.Run("validator voted in by majority", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + proposedAddress = std.Address("proposal") + proposedKey = "proposed-key" + + initialSet = generateTestValidators(3) + ) + + // Prepare the initial validator set + for index, validator := range initialSet { + validator.Address = std.Address(ufmt.Sprintf("caller-%d", index)) + validator.PubKey = ufmt.Sprintf("caller-key-%d", index) + validator.VotingPower = 1 + } + + // Create the protocol with no initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Add in the votes + for _, validator := range initialSet { + // Set the origin caller + std.TestSetOrigCaller(validator.Address) + + // Attempt to add the validator + testing.NotPanics(t, func() { + p.AddValidator(proposedAddress, proposedKey) + }) + } + + // Make sure the validator is added + if !p.IsValidator(proposedAddress) { + t.Fatal("address is not validator") + } + + // Make sure the total voting power is changed + if p.totalVotingPower != 4 { + t.Fatal("invalid total voting power") + } + + // Make sure the majority voting power is changed + if p.majorityPower != 2 { // 2 * 4 / 3 + t.Fatal("invalid majority voting power") + } + }) +} + +func TestPoA_RemoveValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("validator not in set", func(t *testing.T) { + t.Parallel() + + callerAddress := std.Address("caller") + + // Create the protocol with no initial set + p := NewPoA() + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to remove the validator + testing.PanicsWithError(t, errCallerNotValidator, func() { + p.RemoveValidator(callerAddress) + }) + }) + + t.Run("proposed removal not in set", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = callerAddress + + // Create the protocol with no initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to remove the validator + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + p.RemoveValidator(std.Address("totally random")) + }) + }) +} + +func TestPoA_RemoveValidator(t *testing.T) { + t.Parallel() + + t.Run("validator voted out by single node", func(t *testing.T) { + t.Parallel() + + var ( + callerAddress = std.Address("caller") + callerKey = "public-key" + + initialSet = generateTestValidators(2) + ) + + initialSet[0].Address = callerAddress + initialSet[0].PubKey = callerKey + initialSet[0].VotingPower = 1 + + proposedAddress := initialSet[1].Address + + // Create the protocol with no initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Set the origin caller + std.TestSetOrigCaller(callerAddress) + + // Attempt to remove the validator + testing.NotPanics(t, func() { + p.RemoveValidator(proposedAddress) + }) + + // Make sure the validator is removed + if p.IsValidator(proposedAddress) { + t.Fatal("address is validator") + } + + // Make sure the total voting power is changed + if p.totalVotingPower != 1 { + t.Fatal("invalid total voting power") + } + + // Make sure the majority voting power is changed + if p.majorityPower != 0 { + t.Fatal("invalid majority voting power") + } + }) + + t.Run("validator voted in by majority", func(t *testing.T) { + t.Parallel() + + initialSet := generateTestValidators(4) + + // Prepare the initial validator set + for index, validator := range initialSet { + validator.Address = std.Address(ufmt.Sprintf("caller-%d", index)) + validator.PubKey = ufmt.Sprintf("caller-key-%d", index) + validator.VotingPower = 1 + } + + var ( + proposedAddress = initialSet[len(initialSet)-1].Address // last validator + voters = initialSet[:len(initialSet)-1] + ) + + // Create the protocol with no initial set + p := NewPoA(WithInitialSet(initialSet)) + + // Add in the votes + for _, validator := range voters { + // Set the origin caller + std.TestSetOrigCaller(validator.Address) + + // Attempt to remove the validator + testing.NotPanics(t, func() { + p.RemoveValidator(proposedAddress) + }) + } + + // Make sure the validator is removed + if p.IsValidator(proposedAddress) { + t.Fatal("address is validator") + } + + // Make sure the total voting power is changed + if p.totalVotingPower != 3 { + t.Fatal("invalid total voting power") + } + + // Make sure the majority voting power is changed + if p.majorityPower != 2 { // 2 * 3 / 3 + t.Fatal("invalid majority voting power") + } + }) +} diff --git a/examples/gno.land/r/sys/vals/poa/vote.gno b/examples/gno.land/r/sys/vals/poa/vote.gno index e642b2375b4..3a8ebbcc99a 100644 --- a/examples/gno.land/r/sys/vals/poa/vote.gno +++ b/examples/gno.land/r/sys/vals/poa/vote.gno @@ -8,15 +8,17 @@ import ( ) type ( - tallyMap map[std.Address]tally - haveVotedMap map[string]struct{} + tallyMap map[std.Address]tally // address -> current tally + haveVotedMap map[string]struct{} // quick lookup map ) +// tally keeps track of active for / against votes type tally struct { toAdd uint64 toRemove uint64 } +// votingSystem wraps the validator voting type votingSystem struct { tallyMap tallyMap haveVotedMap haveVotedMap @@ -75,12 +77,6 @@ func (v *votingSystem) getTally(candidate std.Address) (uint64, uint64) { return t.toAdd, t.toRemove } -// reset removes all candidates from the voting system -func (v *votingSystem) reset() { - v.tallyMap = make(tallyMap) - v.haveVotedMap = make(haveVotedMap) -} - // resetCandidate removes a candidate from the voting system func (v *votingSystem) resetCandidate(candidate std.Address) { // Drop the tally entries diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index b3c91395625..78da5781960 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -7,17 +7,13 @@ import ( "gno.land/r/sys/vals/types" ) -var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs - -const ( - errCallerNotUserAccount = "caller not user account" - errValidatorExists = "validator already exists" - errValidatorMissing = "validator doesn't exists" +var ( errMaximumValidatorCount = "maximum validator count reached" errMinimumValidatorCount = "minimum validator count reached" - errValidatorNotOrigin = "validator address is not origin" ) +var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs + const ( minValidatorCount = 4 // the absolute minimum number of validators in the set maxValidatorCount = 150 // the practical maximum number of validators in the set @@ -67,30 +63,13 @@ func NewPoS(opts ...Option) *PoS { // - caller has staked funds >= the current staking threshold // - the validator set is less than the maximum size func (p *PoS) AddValidator(address std.Address, pubKey string) { - // Check if the caller is a user account - if !std.PrevRealm().IsUser() { - panic(errCallerNotUserAccount) - } + caller := std.GetOrigCaller() - // Check if the validator is already in the set - if p.IsValidator(address) { - panic(errValidatorExists) - } + // Validate the addition + p.validateAdd(caller, address) - // Check if the limit is reached - if len(p.validators) == maxValidatorCount { - panic(errMaximumValidatorCount) - } - - var ( - caller = std.GetOrigCaller() - stakedAmount = std.GetOrigSend().AmountOf("ugnot") - ) - - // Check if the caller is supplying their own address - if address != caller { - panic(errValidatorNotOrigin) - } + // Extract the sent amount with the call + stakedAmount := std.GetOrigSend().AmountOf("ugnot") // Fetch the already staked amount addressStake, exists := p.addressToStakedAmount[caller] @@ -132,6 +111,29 @@ func (p *PoS) AddValidator(address std.Address, pubKey string) { ) } +// validateAdd validates a validator add call +func (p *PoS) validateAdd(caller, address std.Address) { + // Check if the caller is a user account + if !std.PrevRealm().IsUser() { + panic(types.ErrCallerNotUserAccount) + } + + // Check if the validator is already in the set + if p.IsValidator(address) { + panic(types.ErrValidatorExists) + } + + // Check if the limit is reached + if len(p.validators) == maxValidatorCount { + panic(errMaximumValidatorCount) + } + + // Check if the caller is supplying their own address + if address != caller { + panic(types.ErrValidatorNotOrigin) + } +} + // rebalanceVotingPower rebalances the voting power of the validator set func (p *PoS) rebalanceVotingPower() { for _, v := range p.validators { @@ -160,12 +162,12 @@ func (p *PoS) canBecomeValidator(address std.Address) bool { func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is a user account if !std.PrevRealm().IsUser() { - panic(errCallerNotUserAccount) + panic(types.ErrCallerNotUserAccount) } // Check if this request came from a validator if !p.IsValidator(address) { - panic(errValidatorMissing) + panic(types.ErrValidatorMissing) } // Check if the limit is reached @@ -180,7 +182,7 @@ func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is supplying their own address if address != caller.String() { - panic(errValidatorNotOrigin) + panic(types.ErrValidatorNotOrigin) } // Check if the validator is in the set diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno index 8ee2ead41c2..0f7b42e6ab6 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -48,7 +48,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - testing.PanicsWithError(t, errValidatorExists, func() { + testing.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -92,7 +92,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(std.Address("random address")) // Attempt to add the validator - testing.PanicsWithError(t, errValidatorNotOrigin, func() { + testing.PanicsWithError(t, types.ErrValidatorNotOrigin, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -262,7 +262,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, errValidatorMissing, func() { + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(callerAddress) }) }) @@ -309,7 +309,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, errValidatorNotOrigin, func() { + testing.PanicsWithError(t, types.ErrValidatorNotOrigin, func() { p.RemoveValidator(initialSet[0].Address) }) }) diff --git a/examples/gno.land/r/sys/vals/types/errors.gno b/examples/gno.land/r/sys/vals/types/errors.gno new file mode 100644 index 00000000000..3f1b9a93c6e --- /dev/null +++ b/examples/gno.land/r/sys/vals/types/errors.gno @@ -0,0 +1,8 @@ +package types + +const ( + ErrCallerNotUserAccount = "caller not user account" + ErrValidatorExists = "validator already exists" + ErrValidatorMissing = "validator doesn't exist" + ErrValidatorNotOrigin = "validator address is not origin" +) From ee678fe7453bb2edab90249b57ac516a8d01ebbe Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 24 May 2024 17:09:55 +0200 Subject: [PATCH 10/71] Cleanup --- examples/gno.land/r/sys/vals/gno.mod | 2 +- examples/gno.land/r/sys/vals/poa/poa.gno | 6 +-- examples/gno.land/r/sys/vals/pos/pos.gno | 26 ++-------- examples/gno.land/r/sys/vals/pos/pos_test.gno | 51 +------------------ examples/gno.land/r/sys/vals/types/errors.gno | 8 --- examples/gno.land/r/sys/vals/types/types.gno | 11 ++++ examples/gno.land/r/sys/vals/vals.gno | 4 +- 7 files changed, 20 insertions(+), 88 deletions(-) delete mode 100644 examples/gno.land/r/sys/vals/types/errors.gno diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index 227441ae7c2..25cddc8fb49 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -3,6 +3,6 @@ module gno.land/r/sys/vals require ( gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/pos v0.0.0-latest + gno.land/r/sys/poa v0.0.0-latest gno.land/r/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/r/sys/vals/poa/poa.gno index 817e2f177ee..e3c55410d4a 100644 --- a/examples/gno.land/r/sys/vals/poa/poa.gno +++ b/examples/gno.land/r/sys/vals/poa/poa.gno @@ -6,10 +6,6 @@ import ( "gno.land/r/sys/vals/types" ) -// voteWipeThreshold is the interval in which -// all active votes are wiped (proposal reset every X blocks) -const voteWipeThreshold = 500 // blocks - const ( UserVotedAddEvent = "UserVotedAdd" // emitted when someone votes to add a validator UserVotedRemoveEvent = "UserVotedRemove" // emitted when someone votes to remove a validator @@ -86,7 +82,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string) { v := &types.Validator{ Address: address, PubKey: pubKey, // TODO: in the future, verify the public key - VotingPower: 1, // in this PoA system, everyone has the same voting power + VotingPower: 1, // in this PoA system, all new validators have the same voting power } p.addValidator(v) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index 78da5781960..5296a499f8a 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -7,18 +7,10 @@ import ( "gno.land/r/sys/vals/types" ) -var ( - errMaximumValidatorCount = "maximum validator count reached" - errMinimumValidatorCount = "minimum validator count reached" -) +var errValidatorNotOrigin = "validator address is not origin" var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs -const ( - minValidatorCount = 4 // the absolute minimum number of validators in the set - maxValidatorCount = 150 // the practical maximum number of validators in the set -) - const ( UserStakedEvent = "UserStaked" // emitted when a user stakes funds UserUnstakedEvent = "UserUnstaked" // emitted when a user unstakes funds @@ -61,7 +53,6 @@ func NewPoS(opts ...Option) *PoS { // - caller is a user account // - caller is not already a validator // - caller has staked funds >= the current staking threshold -// - the validator set is less than the maximum size func (p *PoS) AddValidator(address std.Address, pubKey string) { caller := std.GetOrigCaller() @@ -123,14 +114,9 @@ func (p *PoS) validateAdd(caller, address std.Address) { panic(types.ErrValidatorExists) } - // Check if the limit is reached - if len(p.validators) == maxValidatorCount { - panic(errMaximumValidatorCount) - } - // Check if the caller is supplying their own address if address != caller { - panic(types.ErrValidatorNotOrigin) + panic(errValidatorNotOrigin) } } @@ -158,7 +144,6 @@ func (p *PoS) canBecomeValidator(address std.Address) bool { // Criteria for being removed: // - caller is a user account // - caller is a validator -// - the validator set is more than the minimum size func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is a user account if !std.PrevRealm().IsUser() { @@ -170,11 +155,6 @@ func (p *PoS) RemoveValidator(address std.Address) { panic(types.ErrValidatorMissing) } - // Check if the limit is reached - if len(p.validators) == minValidatorCount { - panic(errMinimumValidatorCount) - } - var ( caller = std.GetOrigCaller() addressStake = p.addressToStakedAmount[caller] @@ -182,7 +162,7 @@ func (p *PoS) RemoveValidator(address std.Address) { // Check if the caller is supplying their own address if address != caller.String() { - panic(types.ErrValidatorNotOrigin) + panic(errValidatorNotOrigin) } // Check if the validator is in the set diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno index 0f7b42e6ab6..3900a30c5a6 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -53,28 +53,6 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { }) }) - t.Run("validator set full", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(maxValidatorCount) - ) - - // Create the protocol with an initial set - p := NewPoS(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to add the validator - testing.PanicsWithError(t, errMaximumValidatorCount, func() { - p.AddValidator(callerAddress, callerKey) - }) - }) - t.Run("validator address is not the origin", func(t *testing.T) { t.Parallel() @@ -92,7 +70,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(std.Address("random address")) // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorNotOrigin, func() { + testing.PanicsWithError(t, errValidatorNotOrigin, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -267,31 +245,6 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { }) }) - t.Run("validator set at minimum", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(minValidatorCount) - ) - - initialSet[0].Address = callerAddress - initialSet[0].PubKey = callerKey - - // Create the protocol with an initial set - p := NewPoS(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to remove the validator - testing.PanicsWithError(t, errMinimumValidatorCount, func() { - p.RemoveValidator(callerAddress) - }) - }) - t.Run("validator address is not the origin", func(t *testing.T) { t.Parallel() @@ -309,7 +262,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorNotOrigin, func() { + testing.PanicsWithError(t, errValidatorNotOrigin, func() { p.RemoveValidator(initialSet[0].Address) }) }) diff --git a/examples/gno.land/r/sys/vals/types/errors.gno b/examples/gno.land/r/sys/vals/types/errors.gno deleted file mode 100644 index 3f1b9a93c6e..00000000000 --- a/examples/gno.land/r/sys/vals/types/errors.gno +++ /dev/null @@ -1,8 +0,0 @@ -package types - -const ( - ErrCallerNotUserAccount = "caller not user account" - ErrValidatorExists = "validator already exists" - ErrValidatorMissing = "validator doesn't exist" - ErrValidatorNotOrigin = "validator address is not origin" -) diff --git a/examples/gno.land/r/sys/vals/types/types.gno b/examples/gno.land/r/sys/vals/types/types.gno index 4d786debb92..6f2a5bc7d93 100644 --- a/examples/gno.land/r/sys/vals/types/types.gno +++ b/examples/gno.land/r/sys/vals/types/types.gno @@ -15,3 +15,14 @@ const ( ValidatorAddedEvent = "ValidatorAdded" // emitted when a validator was added to the set ValidatorRemovedEvent = "ValidatorRemoved" // emitted when a validator was removed from the set ) + +const ( + // ErrCallerNotUserAccount is returned when the caller is not a user account + ErrCallerNotUserAccount = "caller not user account" + + // ErrValidatorExists is returned when the validator is already in the set + ErrValidatorExists = "validator already exists" + + // ErrValidatorMissing is returned when the validator is not in the set + ErrValidatorMissing = "validator doesn't exist" +) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 07aa8d9e9cc..572ba73dace 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -5,7 +5,7 @@ import ( "gno.land/p/demo/json" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/pos" + "gno.land/r/sys/poa" "gno.land/r/sys/vals/types" ) @@ -35,7 +35,7 @@ type Protocol interface { } // p is the underlying validator set protocol (PoA / PoS) -var p Protocol = pos.NewPoS() +var p Protocol = poa.NewPoA() // AddValidator adds a new validator to the validator set. // If the validator is already present, the method errors out From fe8ac455a4a333adbe195f9486997eec52f6d150 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 24 May 2024 17:24:09 +0200 Subject: [PATCH 11/71] Cleanup --- examples/gno.land/r/sys/vals/gno.mod | 2 +- examples/gno.land/r/sys/vals/pos/pos.gno | 2 +- examples/gno.land/r/sys/vals/vals.gno | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index 25cddc8fb49..b51fdb1b83f 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -3,6 +3,6 @@ module gno.land/r/sys/vals require ( gno.land/p/demo/json v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/poa v0.0.0-latest + gno.land/r/sys/vals/poa v0.0.0-latest gno.land/r/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index 5296a499f8a..e8ed7bcd314 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -161,7 +161,7 @@ func (p *PoS) RemoveValidator(address std.Address) { ) // Check if the caller is supplying their own address - if address != caller.String() { + if address != caller { panic(errValidatorNotOrigin) } diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 572ba73dace..4812f83c8b8 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -5,7 +5,7 @@ import ( "gno.land/p/demo/json" "gno.land/p/demo/ufmt" - "gno.land/r/sys/poa" + "gno.land/r/sys/vals/poa" "gno.land/r/sys/vals/types" ) From 4c2101214db1c039609e6296d28f7ce5925c318e Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 24 May 2024 17:42:18 +0200 Subject: [PATCH 12/71] Fix stdshim std.Emit --- gnovm/stdlibs/stdshim/stdshim.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/stdlibs/stdshim/stdshim.gno b/gnovm/stdlibs/stdshim/stdshim.gno index 34449f96dd7..47a176296be 100644 --- a/gnovm/stdlibs/stdshim/stdshim.gno +++ b/gnovm/stdlibs/stdshim/stdshim.gno @@ -80,6 +80,6 @@ func DerivePkgAddr(pkgPath string) (addr Address) { panic(shimWarn) } -func Emit(tag, key, value string) { +func Emit(typ string, attrs ...string) { panic(shimWarn) } From 9814fc1693d9321056995eed24831b146f701f6e Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 27 May 2024 16:41:21 +0200 Subject: [PATCH 13/71] Tidy unused methods --- contribs/gnodev/pkg/dev/node.go | 2 +- gno.land/cmd/gnoland/start.go | 2 +- gno.land/pkg/gnoland/app.go | 11 +++++--- gno.land/pkg/gnoland/node_inmemory.go | 36 +++------------------------ tm2/pkg/sdk/baseapp.go | 8 ------ tm2/pkg/sdk/options.go | 15 ----------- 6 files changed, 13 insertions(+), 61 deletions(-) diff --git a/contribs/gnodev/pkg/dev/node.go b/contribs/gnodev/pkg/dev/node.go index 4139c274b82..0da387aaae0 100644 --- a/contribs/gnodev/pkg/dev/node.go +++ b/contribs/gnodev/pkg/dev/node.go @@ -523,7 +523,7 @@ func buildNode(logger *slog.Logger, emitter emitter.Emitter, cfg *gnoland.InMemo func newNodeConfig(tmc *tmcfg.Config, chainid string, appstate gnoland.GnoGenesisState) *gnoland.InMemoryNodeConfig { // Create Mocked Identity pv := gnoland.NewMockedPrivValidator() - genesis := gnoland.NewDefaultGenesisConfig(pv.GetPubKey(), chainid) + genesis := gnoland.NewDefaultGenesisConfig(chainid) genesis.AppState = appstate // Add self as validator diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 282681edd63..65131a334c9 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -236,7 +236,7 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { } // Create application and node - cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger, c.genesisMaxVMCycles) + cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger) if err != nil { return fmt.Errorf("unable to create the Gnoland app, %w", err) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 7669d5cce95..4694a532637 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -56,7 +56,7 @@ func (c *AppOptions) validate() error { return nil } -// NewApp creates the GnoLand application. +// NewAppWithOptions creates the GnoLand application with specified options func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { if err := cfg.validate(); err != nil { return nil, err @@ -126,7 +126,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { } // NewApp creates the GnoLand application. -func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger, maxCycles int64) (abci.Application, error) { +func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger) (abci.Application, error) { var err error cfg := NewAppOptions() @@ -155,7 +155,12 @@ func PanicOnFailingTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { } // InitChainer returns a function that can initialize the chain with genesis. -func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank.BankKeeperI, resHandler GenesisTxHandler) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { +func InitChainer( + baseApp *sdk.BaseApp, + acctKpr auth.AccountKeeperI, + bankKpr bank.BankKeeperI, + resHandler GenesisTxHandler, +) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { if req.AppState != nil { // Get genesis state diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index b7fe1161605..1e6f929f7f9 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -3,7 +3,6 @@ package gnoland import ( "fmt" "log/slog" - "sync" "time" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" @@ -11,11 +10,9 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/node" "github.com/gnolang/gno/tm2/pkg/bft/proxy" bft "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" - "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -33,8 +30,8 @@ func NewMockedPrivValidator() bft.PrivValidator { return bft.NewMockPVWithParams(ed25519.GenPrivKey(), false, false) } -// NewInMemoryNodeConfig creates a default configuration for an in-memory node. -func NewDefaultGenesisConfig(pk crypto.PubKey, chainid string) *bft.GenesisDoc { +// NewDefaultGenesisConfig creates a default configuration for an in-memory node. +func NewDefaultGenesisConfig(chainid string) *bft.GenesisDoc { return &bft.GenesisDoc{ GenesisTime: time.Now(), ChainID: chainid, @@ -114,8 +111,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, dbProvider := func(*node.DBContext) (db.DB, error) { return memdb.NewMemDB(), nil } - // generate p2p node identity - // XXX: do we need to configur + // Generate p2p node identity nodekey := &p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} // Create and return the in-memory node instance @@ -127,29 +123,3 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, logger, ) } - -// GetNodeReadiness waits until the node is ready, signaling via the EventNewBlock event. -// XXX: This should be replace by https://github.com/gnolang/gno/pull/1216 -func GetNodeReadiness(n *node.Node) <-chan struct{} { - const listenerID = "first_block_listener" - - var once sync.Once - - nb := make(chan struct{}) - ready := func() { - close(nb) - n.EventSwitch().RemoveListener(listenerID) - } - - n.EventSwitch().AddListener(listenerID, func(ev events.Event) { - if _, ok := ev.(bft.EventNewBlock); ok { - once.Do(ready) - } - }) - - if n.BlockStore().Height() > 0 { - once.Do(ready) - } - - return nb -} diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index f7c7f5c73b0..66f6b331948 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -209,14 +209,6 @@ func (app *BaseApp) setMinGasPrices(gasPrices []GasPrice) { app.minGasPrices = gasPrices } -func (app *BaseApp) setHaltHeight(haltHeight uint64) { - app.haltHeight = haltHeight -} - -func (app *BaseApp) setHaltTime(haltTime uint64) { - app.haltTime = haltTime -} - // Returns a read-only (cache) MultiStore. // This may be used by keepers for initialization upon restart. func (app *BaseApp) GetCacheMultiStore() store.MultiStore { diff --git a/tm2/pkg/sdk/options.go b/tm2/pkg/sdk/options.go index 4a00681c727..f174b5501a2 100644 --- a/tm2/pkg/sdk/options.go +++ b/tm2/pkg/sdk/options.go @@ -10,11 +10,6 @@ import ( // File for storing in-package BaseApp optional functions, // for options that need access to non-exported fields of the BaseApp -// SetStoreOptions sets store options on the multistore associated with the app -func SetStoreOptions(opts store.StoreOptions) func(*BaseApp) { - return func(bap *BaseApp) { bap.cms.SetStoreOptions(opts) } -} - // SetPruningOptions sets pruning options on the multistore associated with the app func SetPruningOptions(opts store.PruningOptions) func(*BaseApp) { return func(bap *BaseApp) { @@ -34,16 +29,6 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) } } -// SetHaltHeight returns a BaseApp option function that sets the halt block height. -func SetHaltHeight(blockHeight uint64) func(*BaseApp) { - return func(bap *BaseApp) { bap.setHaltHeight(blockHeight) } -} - -// SetHaltTime returns a BaseApp option function that sets the halt block time. -func SetHaltTime(haltTime uint64) func(*BaseApp) { - return func(bap *BaseApp) { bap.setHaltTime(haltTime) } -} - func (app *BaseApp) SetName(name string) { if app.sealed { panic("SetName() on sealed BaseApp") From 935f7b097502843f9f0693ddc9917e816ed5ee80 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 28 May 2024 12:56:30 +0200 Subject: [PATCH 14/71] Add initial POC code for valset injections in Endblocker --- gno.land/cmd/gnoland/start.go | 6 +- gno.land/pkg/gnoland/app.go | 36 ++++++-- gno.land/pkg/gnoland/events.go | 58 +++++++++++++ gno.land/pkg/gnoland/vals.go | 122 +++++++++++++++++++++++++++ gnovm/stdlibs/std/emit_event.gno | 2 +- gnovm/stdlibs/std/emit_event.go | 17 ++-- gnovm/stdlibs/std/emit_event_test.go | 24 +++--- gnovm/stdlibs/std/package.go | 4 +- tm2/pkg/bft/state/execution.go | 2 +- tm2/pkg/sdk/baseapp.go | 11 ++- 10 files changed, 243 insertions(+), 39 deletions(-) create mode 100644 gno.land/pkg/gnoland/events.go create mode 100644 gno.land/pkg/gnoland/vals.go diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index 65131a334c9..eeaecd685ac 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -23,6 +23,7 @@ import ( bft "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/events" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/telemetry" @@ -235,8 +236,11 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { return fmt.Errorf("unable to initialize telemetry, %w", err) } + // Create a top-level event switch + eventSwitch := events.NewEventSwitch() + // Create application and node - cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger) + cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger, eventSwitch) if err != nil { return fmt.Errorf("unable to create the Gnoland app, %w", err) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 4694a532637..8efb79ce4d3 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -10,6 +10,7 @@ import ( abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/config" dbm "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" @@ -32,6 +33,7 @@ type AppOptions struct { GnoRootDir string GenesisTxHandler GenesisTxHandler Logger *slog.Logger + EventSwitch events.EventSwitch MaxCycles int64 } @@ -41,6 +43,7 @@ func NewAppOptions() *AppOptions { Logger: log.NewNoopLogger(), DB: memdb.NewMemDB(), GnoRootDir: gnoenv.RootDir(), + EventSwitch: events.NilEventSwitch(), } } @@ -106,8 +109,14 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { }, ) + // Set up the event collector + c := newCollector[abci.ValidatorUpdate]( + cfg.EventSwitch, // global event switch filled by the node + validatorEventFilter, // filter fn that keeps the collector valid + ) + // Set EndBlocker - baseApp.SetEndBlocker(EndBlocker(vmKpr)) + baseApp.SetEndBlocker(EndBlocker(c)) // Set a handler Route. baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) @@ -126,7 +135,12 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { } // NewApp creates the GnoLand application. -func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger) (abci.Application, error) { +func NewApp( + dataRootDir string, + skipFailingGenesisTxs bool, + logger *slog.Logger, + evsw events.EventSwitch, +) (abci.Application, error) { var err error cfg := NewAppOptions() @@ -141,14 +155,16 @@ func NewApp(dataRootDir string, skipFailingGenesisTxs bool, logger *slog.Logger) } cfg.Logger = logger + cfg.EventSwitch = evsw + return NewAppWithOptions(cfg) } type GenesisTxHandler func(ctx sdk.Context, tx std.Tx, res sdk.Result) -func NoopGenesisTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) {} +func NoopGenesisTxHandler(_ sdk.Context, _ std.Tx, _ sdk.Result) {} -func PanicOnFailingTxHandler(ctx sdk.Context, tx std.Tx, res sdk.Result) { +func PanicOnFailingTxHandler(_ sdk.Context, _ std.Tx, res sdk.Result) { if res.IsErr() { panic(res.Log) } @@ -199,9 +215,13 @@ func InitChainer( } } -// XXX not used yet. -func EndBlocker(vmk vm.VMKeeperI) func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return abci.ResponseEndBlock{} +// EndBlocker defines the logic executed after every block. +// Currently, it parses events that happened during execution to calculate +// validator set changes +func EndBlocker(collector *collector[abci.ValidatorUpdate]) func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + return func(_ sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { + return abci.ResponseEndBlock{ + ValidatorUpdates: collector.getEvents(), + } } } diff --git a/gno.land/pkg/gnoland/events.go b/gno.land/pkg/gnoland/events.go new file mode 100644 index 00000000000..74cbad5aaac --- /dev/null +++ b/gno.land/pkg/gnoland/events.go @@ -0,0 +1,58 @@ +package gnoland + +import ( + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/rs/xid" +) + +// eventType encompasses all event types +// that can appear in the collector +type eventType interface { + abci.ValidatorUpdate +} + +// filterFn is the filter method for incoming events +type filterFn[T eventType] func(events.Event) []T + +// collector is the generic in-memory event collector +type collector[T eventType] struct { + events []T // temporary event storage + filter filterFn[T] // method used for filtering events +} + +// newCollector creates a new event collector +func newCollector[T eventType]( + evsw events.EventSwitch, + filter filterFn[T], +) *collector[T] { + c := &collector[T]{ + events: make([]T, 0), + filter: filter, + } + + // Register the listener + evsw.AddListener(xid.New().String(), func(e events.Event) { + c.updateWith(e) + }) + + return c +} + +// updateWith updates the collector with the given event +func (c *collector[T]) updateWith(event events.Event) { + if extracted := c.filter(event); extracted != nil { + c.events = append(c.events, extracted...) + } +} + +// getEvents returns the filtered events, +// and resets the collector store +func (c *collector[T]) getEvents() []T { + capturedEvents := make([]T, len(c.events)) + copy(capturedEvents, c.events) + + c.events = c.events[:0] + + return capturedEvents +} diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go new file mode 100644 index 00000000000..5eac2435ce1 --- /dev/null +++ b/gno.land/pkg/gnoland/vals.go @@ -0,0 +1,122 @@ +package gnoland + +import ( + "fmt" + "strconv" + + gnovm "github.com/gnolang/gno/gnovm/stdlibs/std" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/events" +) + +const ( + valRealm = "gno.land/r/sys/vals" + + validatorAddedEvent = "ValidatorAdded" + validatorRemovedEvent = "ValidatorRemoved" + + addressEventKey = "address" + pubKeyEventKey = "pub_key" + votingPowerEventKey = "voting_power" +) + +// validatorEventFilter +func validatorEventFilter(event events.Event) []abci.ValidatorUpdate { + // Make sure the event is a new TX event + txResult, ok := event.(types.EventTx) + if !ok { + return nil + } + + // extractValUpdate parses the event attributes and extracts the relevant + // validator change data + extractValUpdate := func(attributes []gnovm.GnoEventAttribute) (*abci.ValidatorUpdate, error) { + // Extract the event attributes + attrs := extractEventAttributes(attributes) + + var ( + addressRaw = attrs[addressEventKey] + pubKeyRaw = attrs[pubKeyEventKey] + votingPowerRaw = attrs[votingPowerEventKey] + ) + + // Parse the address + address, err := crypto.AddressFromBech32(addressRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse address, %w", err) + } + + // Parse the public key + pubKey, err := crypto.PubKeyFromBech32(pubKeyRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse public key, %w", err) + } + + // Parse the voting power + votingPower, err := strconv.Atoi(votingPowerRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse voting power, %w", err) + } + + return &abci.ValidatorUpdate{ + Address: address, + PubKey: pubKey, + Power: int64(votingPower), + }, nil + } + + // Extract the validator change events + valUpdates := make([]abci.ValidatorUpdate, 0) + for _, ev := range txResult.Result.Response.Events { + // Make sure the event is a GnoVM event + gnoEv, ok := ev.(gnovm.GnoEvent) + if !ok { + continue + } + + // Make sure the event is from `r/sys/vals` + if gnoEv.PkgPath != valRealm { + continue + } + + // Make sure the event is either an add / remove + switch gnoEv.Type { + case validatorAddedEvent: + update, err := extractValUpdate(gnoEv.Attributes) + if err != nil { + continue + } + + valUpdates = append(valUpdates, *update) + case validatorRemovedEvent: + update, err := extractValUpdate(gnoEv.Attributes) + if err != nil { + continue + } + + // Validator updates that have Power == 0 + // are considered to be "remove" signals + update.Power = 0 + + valUpdates = append(valUpdates, *update) + default: + continue + } + } + + return valUpdates +} + +// extractEventAttributes generates an attribute map from +// the gno event attributes, for quick lookup +func extractEventAttributes(evAttrs []gnovm.GnoEventAttribute) map[string]string { + attrs := make(map[string]string, len(evAttrs)) + + for _, attr := range evAttrs { + attrs[attr.Key] = attr.Value + } + + return attrs +} diff --git a/gnovm/stdlibs/std/emit_event.gno b/gnovm/stdlibs/std/emit_event.gno index 147513e962b..1c77f903d88 100644 --- a/gnovm/stdlibs/std/emit_event.gno +++ b/gnovm/stdlibs/std/emit_event.gno @@ -1,6 +1,6 @@ package std -// Emit is a function that constructs a gnoEvent with a specified type and attributes. +// Emit is a function that constructs a GnoEvent with a specified type and attributes. // It then forwards this event to the event logger. Each emitted event carries metadata // such as the event type, the initializing realm, and the provided attributes. // diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go index 8e61f67d58a..6f00c19f7c8 100644 --- a/gnovm/stdlibs/std/emit_event.go +++ b/gnovm/stdlibs/std/emit_event.go @@ -20,24 +20,25 @@ func X_emit(m *gno.Machine, typ string, attrs []string) { _, pkgPath := currentRealm(m) fnIdent := getPrevFunctionNameFromTarget(m, "Emit") - evt := gnoEvent{ + evt := GnoEvent{ Type: typ, PkgPath: pkgPath, Func: fnIdent, Attributes: eventAttrs, } + ctx := m.Context.(ExecContext) ctx.EventLogger.EmitEvent(evt) } -func attrKeysAndValues(attrs []string) ([]gnoEventAttribute, error) { +func attrKeysAndValues(attrs []string) ([]GnoEventAttribute, error) { attrLen := len(attrs) if attrLen%2 != 0 { return nil, errInvalidGnoEventAttrs } - eventAttrs := make([]gnoEventAttribute, attrLen/2) + eventAttrs := make([]GnoEventAttribute, attrLen/2) for i := 0; i < attrLen-1; i += 2 { - eventAttrs[i/2] = gnoEventAttribute{ + eventAttrs[i/2] = GnoEventAttribute{ Key: attrs[i], Value: attrs[i+1], } @@ -45,16 +46,16 @@ func attrKeysAndValues(attrs []string) ([]gnoEventAttribute, error) { return eventAttrs, nil } -type gnoEvent struct { +type GnoEvent struct { Type string `json:"type"` PkgPath string `json:"pkg_path"` Func string `json:"func"` - Attributes []gnoEventAttribute `json:"attrs"` + Attributes []GnoEventAttribute `json:"attrs"` } -func (e gnoEvent) AssertABCIEvent() {} +func (e GnoEvent) AssertABCIEvent() {} -type gnoEventAttribute struct { +type GnoEventAttribute struct { Key string `json:"key"` Value string `json:"value"` } diff --git a/gnovm/stdlibs/std/emit_event_test.go b/gnovm/stdlibs/std/emit_event_test.go index 147ad75dbb5..cbe3fcea19e 100644 --- a/gnovm/stdlibs/std/emit_event_test.go +++ b/gnovm/stdlibs/std/emit_event_test.go @@ -21,19 +21,19 @@ func TestEmit(t *testing.T) { name string eventType string attrs []string - expectedEvents []gnoEvent + expectedEvents []GnoEvent expectPanic bool }{ { name: "SimpleValid", eventType: "test", attrs: []string{"key1", "value1", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "test", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -51,12 +51,12 @@ func TestEmit(t *testing.T) { name: "EmptyAttribute", eventType: "test", attrs: []string{"key1", "", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "test", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: ""}, {Key: "key2", Value: "value2"}, }, @@ -68,12 +68,12 @@ func TestEmit(t *testing.T) { name: "EmptyType", eventType: "", attrs: []string{"key1", "value1", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -85,12 +85,12 @@ func TestEmit(t *testing.T) { name: "EmptyAttributeKey", eventType: "test", attrs: []string{"", "value1", "key2", "value2"}, - expectedEvents: []gnoEvent{ + expectedEvents: []GnoEvent{ { Type: "test", PkgPath: pkgPath, Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -148,12 +148,12 @@ func TestEmit_MultipleEvents(t *testing.T) { t.Fatal(err) } - expect := []gnoEvent{ + expect := []GnoEvent{ { Type: "test1", PkgPath: "", Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key1", Value: "value1"}, {Key: "key2", Value: "value2"}, }, @@ -162,7 +162,7 @@ func TestEmit_MultipleEvents(t *testing.T) { Type: "test2", PkgPath: "", Func: "", - Attributes: []gnoEventAttribute{ + Attributes: []GnoEventAttribute{ {Key: "key3", Value: "value3"}, {Key: "key4", Value: "value4"}, }, diff --git a/gnovm/stdlibs/std/package.go b/gnovm/stdlibs/std/package.go index 219f196b2d9..05d30cb38da 100644 --- a/gnovm/stdlibs/std/package.go +++ b/gnovm/stdlibs/std/package.go @@ -14,6 +14,6 @@ var Package = amino.RegisterPackage(amino.NewPackage( abci.Package, ). WithTypes( - gnoEventAttribute{}, - gnoEvent{}, + GnoEventAttribute{}, + GnoEvent{}, )) diff --git a/tm2/pkg/bft/state/execution.go b/tm2/pkg/bft/state/execution.go index da1735e3fae..8b461cdbf6c 100644 --- a/tm2/pkg/bft/state/execution.go +++ b/tm2/pkg/bft/state/execution.go @@ -427,7 +427,7 @@ func fireEvents(evsw events.EventSwitch, block *types.Block, abciResponses *ABCI Height: block.Height, Index: uint32(i), Tx: tx, - Response: (abciResponses.DeliverTxs[i]), + Response: abciResponses.DeliverTxs[i], }}) } diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index 66f6b331948..88cf2b4d3c5 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -622,7 +622,8 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res data := make([]byte, 0, len(msgs)) err := error(nil) - events := []Event{} + + var events []Event // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. for i, msg := range msgs { @@ -682,9 +683,7 @@ func (app *BaseApp) getState(mode RunTxMode) *state { // cacheTxContext returns a new context based off of the provided context with // a cache wrapped multi-store. -func (app *BaseApp) cacheTxContext(ctx Context, txBytes []byte) ( - Context, store.MultiStore, -) { +func (app *BaseApp) cacheTxContext(ctx Context) (Context, store.MultiStore) { ms := ctx.MultiStore() // TODO: https://github.com/tendermint/classic/sdk/issues/2824 msCache := ms.MultiCacheWrap() @@ -789,7 +788,7 @@ func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) // aborted/failed. This may have some performance // benefits, but it'll be more difficult to get // right. - anteCtx, msCache = app.cacheTxContext(ctx, txBytes) + anteCtx, msCache = app.cacheTxContext(ctx) // Call AnteHandler. // NOTE: It is the responsibility of the anteHandler // to use something like passthroughGasMeter to @@ -817,7 +816,7 @@ func (app *BaseApp) runTx(mode RunTxMode, txBytes []byte, tx Tx) (result Result) // Create a new context based off of the existing context with a cache wrapped // multi-store in case message processing fails. - runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) + runMsgCtx, msCache := app.cacheTxContext(ctx) result = app.runMsgs(runMsgCtx, msgs, mode) result.GasWanted = gasWanted From 997f6af02afdbcbb4048bf302c119c7b9c6f7c10 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 3 Jun 2024 11:36:33 +0200 Subject: [PATCH 15/71] Apply suggestions for set scraping --- examples/gno.land/r/sys/vals/poa/poa.gno | 54 +++++++++------------ examples/gno.land/r/sys/vals/pos/pos.gno | 36 +++++--------- examples/gno.land/r/sys/vals/vals.gno | 61 +++++++++++++++++++----- 3 files changed, 83 insertions(+), 68 deletions(-) diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/r/sys/vals/poa/poa.gno index e3c55410d4a..1d0744fee79 100644 --- a/examples/gno.land/r/sys/vals/poa/poa.gno +++ b/examples/gno.land/r/sys/vals/poa/poa.gno @@ -52,7 +52,7 @@ func NewPoA(opts ...Option) *PoA { // - caller is a validator // - the proposed address is not a validator // - a 2/3+ majority of the set have voted to add -func (p *PoA) AddValidator(address std.Address, pubKey string) { +func (p *PoA) AddValidator(address std.Address, pubKey string) *types.Validator { caller := std.GetOrigCaller() // Validate that the operation is a valid call @@ -62,11 +62,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string) { p.votes.castVote(caller, address, true) // Emit the vote event - std.Emit( - UserVotedAddEvent, - "address", address.String(), - "voter", caller.String(), - ) + std.Emit(UserVotedAddEvent) // Calculate the votes addCount, _ := p.votes.getTally(address) @@ -75,17 +71,11 @@ func (p *PoA) AddValidator(address std.Address, pubKey string) { // to apply the action if addCount <= p.majorityPower { // No super majority to add - return + return nil } // Execute the decision - v := &types.Validator{ - Address: address, - PubKey: pubKey, // TODO: in the future, verify the public key - VotingPower: 1, // in this PoA system, all new validators have the same voting power - } - - p.addValidator(v) + return p.addValidator(address, pubKey) } // validateAdd validates a validator add call @@ -107,7 +97,13 @@ func (p *PoA) validateAdd(caller, address std.Address) { } // addValidator adds the given validator to the PoA validator set -func (p *PoA) addValidator(v *types.Validator) { +func (p *PoA) addValidator(address std.Address, pubKey string) *types.Validator { + v := &types.Validator{ + Address: address, + PubKey: pubKey, // TODO: in the future, verify the public key + VotingPower: 1, // in this PoA system, all new validators have the same voting power + } + // Add the validator to the set p.addressToValidatorIndex[v.Address] = len(p.validators) p.validators = append(p.validators, v) @@ -117,13 +113,12 @@ func (p *PoA) addValidator(v *types.Validator) { p.majorityPower = (2 * p.totalVotingPower) / 3 // Emit the validator set change - std.Emit( - types.ValidatorAddedEvent, - "address", v.Address.String(), - ) + std.Emit(types.ValidatorAddedEvent) // Remove the candidate from the voting system p.votes.resetCandidate(v.Address) + + return v } // RemoveValidator adds a vote to remove a validator from the validator set. @@ -133,7 +128,7 @@ func (p *PoA) addValidator(v *types.Validator) { // - caller is a validator // - the proposed address is a validator // - a 2/3+ majority of the set have voted to remove -func (p *PoA) RemoveValidator(address std.Address) { +func (p *PoA) RemoveValidator(address std.Address) *types.Validator { caller := std.GetOrigCaller() // Validate that the operation is a valid call @@ -143,11 +138,7 @@ func (p *PoA) RemoveValidator(address std.Address) { p.votes.castVote(caller, address, false) // Emit the vote event - std.Emit( - UserVotedRemoveEvent, - "address", address.String(), - "voter", caller.String(), - ) + std.Emit(UserVotedRemoveEvent) // Calculate the votes _, removeCount := p.votes.getTally(address) @@ -156,11 +147,11 @@ func (p *PoA) RemoveValidator(address std.Address) { // to apply the action if removeCount <= p.majorityPower { // No super majority to remove - return + return nil } // Execute the decision - p.removeValidator(address) + return p.removeValidator(address) } // validateRemove validates a validator remove call @@ -182,7 +173,7 @@ func (p *PoA) validateRemove(caller, address std.Address) { } // removeValidator removes the given address from the PoA validator set -func (p *PoA) removeValidator(address std.Address) { +func (p *PoA) removeValidator(address std.Address) *types.Validator { // Fetch the validator index index := p.addressToValidatorIndex[address] @@ -197,13 +188,12 @@ func (p *PoA) removeValidator(address std.Address) { p.majorityPower = (2 * p.totalVotingPower) / 3 // Emit the validator set change - std.Emit( - types.ValidatorRemovedEvent, - "address", address.String(), - ) + std.Emit(types.ValidatorRemovedEvent) // Remove the candidate from the voting system p.votes.resetCandidate(address) + + return validator } // IsValidator returns a flag indicating if the address diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno index e8ed7bcd314..b48710142fb 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -3,7 +3,6 @@ package pos import ( "std" - "gno.land/p/demo/ufmt" "gno.land/r/sys/vals/types" ) @@ -53,7 +52,7 @@ func NewPoS(opts ...Option) *PoS { // - caller is a user account // - caller is not already a validator // - caller has staked funds >= the current staking threshold -func (p *PoS) AddValidator(address std.Address, pubKey string) { +func (p *PoS) AddValidator(address std.Address, pubKey string) *types.Validator { caller := std.GetOrigCaller() // Validate the addition @@ -74,15 +73,11 @@ func (p *PoS) AddValidator(address std.Address, pubKey string) { p.totalStake += uint64(stakedAmount) // Emit the event that the user staked funds - std.Emit( - UserStakedEvent, - "address", address.String(), - "stake", ufmt.Sprintf("%d", addressStake.Amount), - ) + std.Emit(UserStakedEvent) // Check if the caller can become a validator if !p.canBecomeValidator(caller) { - return + return nil } // Add the caller to the validator set @@ -96,10 +91,9 @@ func (p *PoS) AddValidator(address std.Address, pubKey string) { p.validators = append(p.validators, v) // Emit the validator set change - std.Emit( - types.ValidatorAddedEvent, - "address", address.String(), - ) + std.Emit(types.ValidatorAddedEvent) + + return v } // validateAdd validates a validator add call @@ -144,7 +138,7 @@ func (p *PoS) canBecomeValidator(address std.Address) bool { // Criteria for being removed: // - caller is a user account // - caller is a validator -func (p *PoS) RemoveValidator(address std.Address) { +func (p *PoS) RemoveValidator(address std.Address) *types.Validator { // Check if the caller is a user account if !std.PrevRealm().IsUser() { panic(types.ErrCallerNotUserAccount) @@ -165,8 +159,9 @@ func (p *PoS) RemoveValidator(address std.Address) { panic(errValidatorNotOrigin) } - // Check if the validator is in the set + // Get the validator index index := p.addressToValidatorIndex[caller] + validator := p.validators[index] // Remove the validator from the set p.validators = append(p.validators[:index], p.validators[index+1:]...) @@ -178,17 +173,12 @@ func (p *PoS) RemoveValidator(address std.Address) { returnStake(caller, addressStake) // Emit the validator set change - std.Emit( - types.ValidatorRemovedEvent, - "address", address.String(), - ) + std.Emit(types.ValidatorRemovedEvent) // Emit the unstake event - std.Emit( - UserUnstakedEvent, - "address", address.String(), - "stake", ufmt.Sprintf("%d", addressStake.Amount), - ) + std.Emit(UserUnstakedEvent) + + return validator } // returnStake returns the specified stake to the given address diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 4812f83c8b8..1f15877507c 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -17,47 +17,70 @@ type Protocol interface { // TODO: This API is not ideal -- the address should be derived from // the public key, and not be passed in as such, but currently Gno // does not support crypto address derivation - AddValidator(address std.Address, pubKey string) + AddValidator(address std.Address, pubKey string) *types.Validator // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method should error out - RemoveValidator(address std.Address) + RemoveValidator(address std.Address) *types.Validator // IsValidator returns a flag indicating if the given // bech32 address is part of the validator set IsValidator(address std.Address) bool - // GetValidators returns the JSON-encoded validator set. - // The reason for returning JSON, and not a `[]Validator`, - // is because Gno does not yet support an ABI-like functionality - // for clients interacting with the Realm / Package methods + // GetValidators returns the currently active validator set GetValidators() []*types.Validator } -// p is the underlying validator set protocol (PoA / PoS) -var p Protocol = poa.NewPoA() +// vals is the wrapper for the validator set protocol +type vals struct { + p Protocol // p is the underlying validator set protocol (PoA / PoS) + changes []types.Validator // changes are the set changes that happened between scrapes +} + +// v holds the active on-chain validator set state +var v = &vals{ + p: poa.NewPoA(), + changes: make([]types.Validator, 0), +} // AddValidator adds a new validator to the validator set. // If the validator is already present, the method errors out func AddValidator(address, pubKey string) { - p.AddValidator(std.Address(address), pubKey) + if val := v.p.AddValidator(std.Address(address), pubKey); val != nil { + // Validator added, note the change + v.changes = append(v.changes, types.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: val.VotingPower, + }) + } } // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method errors out func RemoveValidator(address string) { - p.RemoveValidator(std.Address(address)) + if val := v.p.RemoveValidator(std.Address(address)); val != nil { + // Validator removed, note the change + v.changes = append(v.changes, types.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: 0, // nullified the voting power indicates removal + }) + } } // IsValidator returns a flag indicating if the given bech32 address // is part of the validator set func IsValidator(address string) bool { - return p.IsValidator(std.Address(address)) + return v.p.IsValidator(std.Address(address)) } -// GetValidators returns the JSON-encoded validator set +// GetValidators returns the JSON-encoded validator set. +// The reason for returning JSON, and not a `[]Validator`, +// is because Gno does not yet support an ABI-like functionality +// for clients interacting with the Realm / Package methods func GetValidators() string { - encodedSet, err := json.Marshal(prepareForJSON(p.GetValidators())) + encodedSet, err := json.Marshal(prepareForJSON(v.p.GetValidators())) if err != nil { panic(ufmt.Sprintf("unable to marshal set, %s", err)) } @@ -65,6 +88,18 @@ func GetValidators() string { return string(encodedSet) } +// getChanges returns the validator changes stored on the realm +func getChanges() []types.Validator { + // Construct the changes + changes := make([]types.Validator, len(v.changes)) + copy(changes, v.changes) + + // Reset the changes set + v.changes = v.changes[:0] + + return changes +} + // prepareForJSON prepares the validator set for JSON encoding func prepareForJSON(vals []*types.Validator) *json.Node { nodes := make([]*json.Node, 0, len(vals)) From 6e61bf850cf717a16e73d70b57c9f2c3a611accb Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 17 Jun 2024 19:04:53 +0200 Subject: [PATCH 16/71] Add PoC --- .../gno.land/r/gov/integration/z_filetest.gno | 2 +- examples/gno.land/r/sys/vals/poa/poa_test.gno | 14 +- examples/gno.land/r/sys/vals/poc/gno.mod | 6 + examples/gno.land/r/sys/vals/poc/option.gno | 15 ++ examples/gno.land/r/sys/vals/poc/poc.gno | 113 ++++++++++++ examples/gno.land/r/sys/vals/poc/poc_test.gno | 171 ++++++++++++++++++ examples/gno.land/r/sys/vals/vals.gno | 4 +- 7 files changed, 315 insertions(+), 10 deletions(-) create mode 100644 examples/gno.land/r/sys/vals/poc/gno.mod create mode 100644 examples/gno.land/r/sys/vals/poc/option.gno create mode 100644 examples/gno.land/r/sys/vals/poc/poc.gno create mode 100644 examples/gno.land/r/sys/vals/poc/poc_test.gno diff --git a/examples/gno.land/r/gov/integration/z_filetest.gno b/examples/gno.land/r/gov/integration/z_filetest.gno index a85588e4f11..23669da7333 100644 --- a/examples/gno.land/r/gov/integration/z_filetest.gno +++ b/examples/gno.land/r/gov/integration/z_filetest.gno @@ -1,7 +1,7 @@ package main import ( - govdao "gno.land/r/gov/dao" + govdao "github.com/gnolang/gno/examples/gno.land/r/gov/dao" _ "gno.land/r/gov/proposals/prop1" ) diff --git a/examples/gno.land/r/sys/vals/poa/poa_test.gno b/examples/gno.land/r/sys/vals/poa/poa_test.gno index 282ecb7a845..9464963e89d 100644 --- a/examples/gno.land/r/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/r/sys/vals/poa/poa_test.gno @@ -16,7 +16,7 @@ func generateTestValidators(count int) []*types.Validator { val := &types.Validator{ Address: std.Address(ufmt.Sprintf("%d", i)), PubKey: "public-key", - VotingPower: 0, + VotingPower: 1, } vals = append(vals, val) @@ -41,7 +41,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { initialSet[0].Address = callerAddress initialSet[0].PubKey = callerKey - // Create the protocol with no initial set + // Create the protocol with an initial set p := NewPoA(WithInitialSet(initialSet)) // Set the origin caller @@ -94,7 +94,7 @@ func TestPoA_AddValidator(t *testing.T) { initialSet[0].PubKey = callerKey initialSet[0].VotingPower = 1 - // Create the protocol with no initial set + // Create the protocol with an initial set p := NewPoA(WithInitialSet(initialSet)) // Set the origin caller @@ -141,7 +141,7 @@ func TestPoA_AddValidator(t *testing.T) { validator.VotingPower = 1 } - // Create the protocol with no initial set + // Create the protocol with an initial set p := NewPoA(WithInitialSet(initialSet)) // Add in the votes @@ -202,7 +202,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { initialSet[0].Address = callerAddress - // Create the protocol with no initial set + // Create the protocol with an initial set p := NewPoA(WithInitialSet(initialSet)) // Set the origin caller @@ -234,7 +234,7 @@ func TestPoA_RemoveValidator(t *testing.T) { proposedAddress := initialSet[1].Address - // Create the protocol with no initial set + // Create the protocol with an initial set p := NewPoA(WithInitialSet(initialSet)) // Set the origin caller @@ -278,7 +278,7 @@ func TestPoA_RemoveValidator(t *testing.T) { voters = initialSet[:len(initialSet)-1] ) - // Create the protocol with no initial set + // Create the protocol with an initial set p := NewPoA(WithInitialSet(initialSet)) // Add in the votes diff --git a/examples/gno.land/r/sys/vals/poc/gno.mod b/examples/gno.land/r/sys/vals/poc/gno.mod new file mode 100644 index 00000000000..5ed131de978 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poc/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals/poc + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/poc/option.gno b/examples/gno.land/r/sys/vals/poc/option.gno new file mode 100644 index 00000000000..a10dc8f0882 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poc/option.gno @@ -0,0 +1,15 @@ +package poc + +import "gno.land/r/sys/vals/types" + +type Option func(*PoC) + +// WithInitialSet sets the initial PoA validator set +func WithInitialSet(validators []*types.Validator) Option { + return func(p *PoC) { + for index, validator := range validators { + p.validators = append(p.validators, validator) + p.addressToValidatorIndex[validator.Address] = index + } + } +} diff --git a/examples/gno.land/r/sys/vals/poc/poc.gno b/examples/gno.land/r/sys/vals/poc/poc.gno new file mode 100644 index 00000000000..79819b3fcb4 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poc/poc.gno @@ -0,0 +1,113 @@ +package poc + +import ( + "std" + + "gno.land/r/sys/vals/types" +) + +const govdaoRealm = "gno.land/r/gov/dao" + +const errNotGovdao = "call not executed by govdao" + +// PoC specifies the Proof of Contribution validator set. +// In order to become part of the set, users to be voted into +// the validator set by a govdao proposal +type PoC struct { + // validators holds the current validator set. + // This slice can never practically grow more than ~150 elements, + // due to Tendermint's quadratic network complexity + validators []*types.Validator + addressToValidatorIndex map[std.Address]int // address -> index +} + +// NewPoC creates a new empty Proof of Contribution validator set +func NewPoC(opts ...Option) *PoC { + // Create the empty set + p := &PoC{ + validators: make([]*types.Validator, 0), + addressToValidatorIndex: make(map[std.Address]int), + } + + // Apply the options + for _, opt := range opts { + opt(p) + } + + return p +} + +func (p *PoC) AddValidator(address std.Address, pubKey string) *types.Validator { + // Validate that the operation is a valid call + p.validateAdd(address) + + v := &types.Validator{ + Address: address, + PubKey: pubKey, // TODO: in the future, verify the public key + VotingPower: 1, // in this PoC system, all new validators have the same voting power + } + + // Add the validator to the set + p.addressToValidatorIndex[v.Address] = len(p.validators) + p.validators = append(p.validators, v) + + // Emit the validator set change + std.Emit(types.ValidatorAddedEvent) + + return v +} + +// validateAdd validates a validator add call +func (p *PoC) validateAdd(address std.Address) { + if std.PrevRealm().PkgPath() != govdaoRealm { + println(std.PrevRealm()) + panic(errNotGovdao) + } + + // Check if the validator is already in the set + if p.IsValidator(address) { + panic(types.ErrValidatorExists) + } +} + +func (p *PoC) RemoveValidator(address std.Address) *types.Validator { + // Validate that the operation is a valid call + p.validateRemove(address) + + // Fetch the validator index + index := p.addressToValidatorIndex[address] + + // Remove the validator from the set + validator := p.validators[index] + p.validators = append(p.validators[:index], p.validators[index+1:]...) + + delete(p.addressToValidatorIndex, address) + + // Emit the validator set change + std.Emit(types.ValidatorRemovedEvent) + + return validator +} + +// validateRemove validates a validator remove call +func (p *PoC) validateRemove(address std.Address) { + // Check if the method call is from a proposal execution + if std.PrevRealm().PkgPath() != govdaoRealm { + panic(errNotGovdao) + } + + // Check if the address is a validator + if !p.IsValidator(address) { + panic(types.ErrValidatorMissing) + } +} + +func (p *PoC) IsValidator(address std.Address) bool { + _, exists := p.addressToValidatorIndex[address] + + return exists +} + +func (p *PoC) GetValidators() []*types.Validator { + return p.validators +} diff --git a/examples/gno.land/r/sys/vals/poc/poc_test.gno b/examples/gno.land/r/sys/vals/poc/poc_test.gno new file mode 100644 index 00000000000..83f2686b042 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poc/poc_test.gno @@ -0,0 +1,171 @@ +package poc + +import ( + "testing" + + "std" + + "gno.land/p/demo/ufmt" + "gno.land/r/sys/vals/types" +) + +// generateTestValidators generates a dummy validator set +func generateTestValidators(count int) []*types.Validator { + vals := make([]*types.Validator, 0, count) + + for i := 0; i < count; i++ { + val := &types.Validator{ + Address: std.Address(ufmt.Sprintf("%d", i)), + PubKey: "public-key", + VotingPower: 1, + } + + vals = append(vals, val) + } + + return vals +} + +func TestPoC_AddValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("call from realm != govdao", func(t *testing.T) { + t.Parallel() + + r := std.NewCodeRealm("gno.land/r/gov/randomdao") + std.TestSetRealm(r) + + var ( + proposalAddress = std.Address("caller") + proposalKey = "public-key" + ) + + // Create the protocol with no initial set + p := NewPoC() + + // Attempt to add the validator + testing.PanicsWithError(t, errNotGovdao, func() { + p.AddValidator(proposalAddress, proposalKey) + }) + }) + + t.Run("validator already in set", func(t *testing.T) { + t.Parallel() + + r := std.NewCodeRealm(govdaoRealm) + std.TestSetRealm(r) + + var ( + proposalAddress = std.Address("caller") + proposalKey = "public-key" + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = proposalAddress + initialSet[0].PubKey = proposalKey + + // Create the protocol with an initial set + p := NewPoC(WithInitialSet(initialSet)) + + // Attempt to add the validator + testing.PanicsWithError(t, types.ErrValidatorExists, func() { + p.AddValidator(proposalAddress, proposalKey) + }) + }) +} + +func TestPoC_AddValidator(t *testing.T) { + t.Parallel() + + r := std.NewCodeRealm(govdaoRealm) + std.TestSetRealm(r) + + var ( + proposalAddress = std.Address("caller") + proposalKey = "public-key" + ) + + // Create the protocol with no initial set + p := NewPoC() + + // Attempt to add the validator + testing.NotPanics(t, func() { + p.AddValidator(proposalAddress, proposalKey) + }) + + // Make sure the validator is added + if !p.IsValidator(proposalAddress) || len(p.validators) != 1 { + t.Fatal("address is not validator") + } +} + +func TestPoC_RemoveValidator_Invalid(t *testing.T) { + t.Parallel() + + t.Run("call from realm != govdao", func(t *testing.T) { + t.Parallel() + + r := std.NewCodeRealm("gno.land/r/gov/randomdao") + std.TestSetRealm(r) + + proposalAddress := std.Address("caller") + + // Create the protocol with no initial set + p := NewPoC() + + // Attempt to add the validator + testing.PanicsWithError(t, errNotGovdao, func() { + p.RemoveValidator(proposalAddress) + }) + }) + + t.Run("proposed removal not in set", func(t *testing.T) { + t.Parallel() + + r := std.NewCodeRealm(govdaoRealm) + std.TestSetRealm(r) + + var ( + proposalAddress = std.Address("caller") + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = proposalAddress + + // Create the protocol with an initial set + p := NewPoC(WithInitialSet(initialSet)) + + // Attempt to remove the validator + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + p.RemoveValidator(std.Address("totally random")) + }) + }) +} + +func TestPoC_RemoveValidator(t *testing.T) { + t.Parallel() + + r := std.NewCodeRealm(govdaoRealm) + std.TestSetRealm(r) + + var ( + proposalAddress = std.Address("caller") + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = proposalAddress + + // Create the protocol with an initial set + p := NewPoC(WithInitialSet(initialSet)) + + // Attempt to remove the validator + testing.NotPanics(t, func() { + p.RemoveValidator(proposalAddress) + }) + + // Make sure the validator is removed + if p.IsValidator(proposalAddress) || len(p.validators) != 0 { + t.Fatal("address is validator") + } +} diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 1f15877507c..11fad8ff0bc 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -5,7 +5,7 @@ import ( "gno.land/p/demo/json" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/poa" + "gno.land/r/sys/vals/poc" "gno.land/r/sys/vals/types" ) @@ -39,7 +39,7 @@ type vals struct { // v holds the active on-chain validator set state var v = &vals{ - p: poa.NewPoA(), + p: poc.NewPoC(), // PoC by default changes: make([]types.Validator, 0), } From 28abe9b21f4a3a7dfcdb8463a6efd28a32ffd117 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 17 Jun 2024 19:14:40 +0200 Subject: [PATCH 17/71] Drop JSON output for valsets --- examples/gno.land/r/sys/vals/gno.mod | 4 +--- examples/gno.land/r/sys/vals/vals.gno | 33 +++------------------------ 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index b51fdb1b83f..ccdc2ade1f9 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -1,8 +1,6 @@ module gno.land/r/sys/vals require ( - gno.land/p/demo/json v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/poa v0.0.0-latest + gno.land/r/sys/vals/poc v0.0.0-latest gno.land/r/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 11fad8ff0bc..c505ebf7864 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -3,8 +3,6 @@ package vals import ( "std" - "gno.land/p/demo/json" - "gno.land/p/demo/ufmt" "gno.land/r/sys/vals/poc" "gno.land/r/sys/vals/types" ) @@ -75,17 +73,9 @@ func IsValidator(address string) bool { return v.p.IsValidator(std.Address(address)) } -// GetValidators returns the JSON-encoded validator set. -// The reason for returning JSON, and not a `[]Validator`, -// is because Gno does not yet support an ABI-like functionality -// for clients interacting with the Realm / Package methods -func GetValidators() string { - encodedSet, err := json.Marshal(prepareForJSON(v.p.GetValidators())) - if err != nil { - panic(ufmt.Sprintf("unable to marshal set, %s", err)) - } - - return string(encodedSet) +// GetValidators returns the typed validator set +func GetValidators() []*types.Validator { + return v.p.GetValidators() } // getChanges returns the validator changes stored on the realm @@ -99,20 +89,3 @@ func getChanges() []types.Validator { return changes } - -// prepareForJSON prepares the validator set for JSON encoding -func prepareForJSON(vals []*types.Validator) *json.Node { - nodes := make([]*json.Node, 0, len(vals)) - - for _, v := range vals { - node := json.ObjectNode("", map[string]*json.Node{ - "address": json.StringNode("address", v.Address.String()), - "pub_key": json.StringNode("pub_key", v.PubKey), - "voting_power": json.NumberNode("voting_power", float64(v.VotingPower)), - }) - - nodes = append(nodes, node) - } - - return json.ArrayNode("", nodes) -} From 26325c2cb256705c2233b8a98c18403c509a7784 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 17 Jun 2024 21:57:15 +0200 Subject: [PATCH 18/71] Cleanup --- examples/gno.land/p/sys/vals/poa/gno.mod | 6 + .../gno.land/{r => p}/sys/vals/poa/option.gno | 2 +- .../gno.land/{r => p}/sys/vals/poa/poa.gno | 2 +- .../{r => p}/sys/vals/poa/poa_test.gno | 2 +- .../gno.land/{r => p}/sys/vals/poa/vote.gno | 0 examples/gno.land/p/sys/vals/poc/gno.mod | 6 + .../gno.land/{r => p}/sys/vals/poc/option.gno | 2 +- .../gno.land/{r => p}/sys/vals/poc/poc.gno | 3 +- .../{r => p}/sys/vals/poc/poc_test.gno | 2 +- examples/gno.land/p/sys/vals/pos/gno.mod | 6 + .../gno.land/{r => p}/sys/vals/pos/option.gno | 2 +- .../gno.land/{r => p}/sys/vals/pos/pos.gno | 2 +- .../{r => p}/sys/vals/pos/pos_test.gno | 2 +- examples/gno.land/p/sys/vals/types/gno.mod | 1 + examples/gno.land/p/sys/vals/types/types.gno | 50 ++++++++ .../gno.land/r/gov/integration/z_filetest.gno | 2 +- .../gno.land/r/gov/proposals/prop1/prop1.gno | 29 +++-- examples/gno.land/r/sys/vals/gno.mod | 4 +- examples/gno.land/r/sys/vals/poa/gno.mod | 6 - examples/gno.land/r/sys/vals/poc.gno | 117 ++++++++++++++++++ examples/gno.land/r/sys/vals/poc/gno.mod | 6 - examples/gno.land/r/sys/vals/pos/gno.mod | 6 - examples/gno.land/r/sys/vals/types/gno.mod | 1 - examples/gno.land/r/sys/vals/types/types.gno | 28 ----- examples/gno.land/r/sys/vals/vals.gno | 91 -------------- 25 files changed, 220 insertions(+), 158 deletions(-) create mode 100644 examples/gno.land/p/sys/vals/poa/gno.mod rename examples/gno.land/{r => p}/sys/vals/poa/option.gno (92%) rename examples/gno.land/{r => p}/sys/vals/poa/poa.gno (99%) rename examples/gno.land/{r => p}/sys/vals/poa/poa_test.gno (99%) rename examples/gno.land/{r => p}/sys/vals/poa/vote.gno (100%) create mode 100644 examples/gno.land/p/sys/vals/poc/gno.mod rename examples/gno.land/{r => p}/sys/vals/poc/option.gno (90%) rename examples/gno.land/{r => p}/sys/vals/poc/poc.gno (98%) rename examples/gno.land/{r => p}/sys/vals/poc/poc_test.gno (99%) create mode 100644 examples/gno.land/p/sys/vals/pos/gno.mod rename examples/gno.land/{r => p}/sys/vals/pos/option.gno (93%) rename examples/gno.land/{r => p}/sys/vals/pos/pos.gno (99%) rename examples/gno.land/{r => p}/sys/vals/pos/pos_test.gno (99%) create mode 100644 examples/gno.land/p/sys/vals/types/gno.mod create mode 100644 examples/gno.land/p/sys/vals/types/types.gno delete mode 100644 examples/gno.land/r/sys/vals/poa/gno.mod create mode 100644 examples/gno.land/r/sys/vals/poc.gno delete mode 100644 examples/gno.land/r/sys/vals/poc/gno.mod delete mode 100644 examples/gno.land/r/sys/vals/pos/gno.mod delete mode 100644 examples/gno.land/r/sys/vals/types/gno.mod delete mode 100644 examples/gno.land/r/sys/vals/types/types.gno delete mode 100644 examples/gno.land/r/sys/vals/vals.gno diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod new file mode 100644 index 00000000000..de760cae626 --- /dev/null +++ b/examples/gno.land/p/sys/vals/poa/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/sys/vals/poa + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/poa/option.gno b/examples/gno.land/p/sys/vals/poa/option.gno similarity index 92% rename from examples/gno.land/r/sys/vals/poa/option.gno rename to examples/gno.land/p/sys/vals/poa/option.gno index 8be3d115c48..d551525daaf 100644 --- a/examples/gno.land/r/sys/vals/poa/option.gno +++ b/examples/gno.land/p/sys/vals/poa/option.gno @@ -1,6 +1,6 @@ package poa -import "gno.land/r/sys/vals/types" +import "gno.land/p/sys/vals/types" type Option func(*PoA) diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/p/sys/vals/poa/poa.gno similarity index 99% rename from examples/gno.land/r/sys/vals/poa/poa.gno rename to examples/gno.land/p/sys/vals/poa/poa.gno index 1d0744fee79..54d43862ba0 100644 --- a/examples/gno.land/r/sys/vals/poa/poa.gno +++ b/examples/gno.land/p/sys/vals/poa/poa.gno @@ -3,7 +3,7 @@ package poa import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) const ( diff --git a/examples/gno.land/r/sys/vals/poa/poa_test.gno b/examples/gno.land/p/sys/vals/poa/poa_test.gno similarity index 99% rename from examples/gno.land/r/sys/vals/poa/poa_test.gno rename to examples/gno.land/p/sys/vals/poa/poa_test.gno index 9464963e89d..536779d32c2 100644 --- a/examples/gno.land/r/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/p/sys/vals/poa/poa_test.gno @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set diff --git a/examples/gno.land/r/sys/vals/poa/vote.gno b/examples/gno.land/p/sys/vals/poa/vote.gno similarity index 100% rename from examples/gno.land/r/sys/vals/poa/vote.gno rename to examples/gno.land/p/sys/vals/poa/vote.gno diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod new file mode 100644 index 00000000000..0263460b501 --- /dev/null +++ b/examples/gno.land/p/sys/vals/poc/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/sys/vals/poc + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/poc/option.gno b/examples/gno.land/p/sys/vals/poc/option.gno similarity index 90% rename from examples/gno.land/r/sys/vals/poc/option.gno rename to examples/gno.land/p/sys/vals/poc/option.gno index a10dc8f0882..ceccc193e7d 100644 --- a/examples/gno.land/r/sys/vals/poc/option.gno +++ b/examples/gno.land/p/sys/vals/poc/option.gno @@ -1,6 +1,6 @@ package poc -import "gno.land/r/sys/vals/types" +import "gno.land/p/sys/vals/types" type Option func(*PoC) diff --git a/examples/gno.land/r/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno similarity index 98% rename from examples/gno.land/r/sys/vals/poc/poc.gno rename to examples/gno.land/p/sys/vals/poc/poc.gno index 79819b3fcb4..9d1075e5d54 100644 --- a/examples/gno.land/r/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -3,7 +3,7 @@ package poc import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) const govdaoRealm = "gno.land/r/gov/dao" @@ -60,7 +60,6 @@ func (p *PoC) AddValidator(address std.Address, pubKey string) *types.Validator // validateAdd validates a validator add call func (p *PoC) validateAdd(address std.Address) { if std.PrevRealm().PkgPath() != govdaoRealm { - println(std.PrevRealm()) panic(errNotGovdao) } diff --git a/examples/gno.land/r/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno similarity index 99% rename from examples/gno.land/r/sys/vals/poc/poc_test.gno rename to examples/gno.land/p/sys/vals/poc/poc_test.gno index 83f2686b042..d2e2345a299 100644 --- a/examples/gno.land/r/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -6,7 +6,7 @@ import ( "std" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set diff --git a/examples/gno.land/p/sys/vals/pos/gno.mod b/examples/gno.land/p/sys/vals/pos/gno.mod new file mode 100644 index 00000000000..585c7b6afc8 --- /dev/null +++ b/examples/gno.land/p/sys/vals/pos/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/sys/vals/pos + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/pos/option.gno b/examples/gno.land/p/sys/vals/pos/option.gno similarity index 93% rename from examples/gno.land/r/sys/vals/pos/option.gno rename to examples/gno.land/p/sys/vals/pos/option.gno index 79602bac232..a179555f79d 100644 --- a/examples/gno.land/r/sys/vals/pos/option.gno +++ b/examples/gno.land/p/sys/vals/pos/option.gno @@ -3,7 +3,7 @@ package pos import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) type Option func(*PoS) diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/p/sys/vals/pos/pos.gno similarity index 99% rename from examples/gno.land/r/sys/vals/pos/pos.gno rename to examples/gno.land/p/sys/vals/pos/pos.gno index b48710142fb..a8caec55ea5 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/p/sys/vals/pos/pos.gno @@ -3,7 +3,7 @@ package pos import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) var errValidatorNotOrigin = "validator address is not origin" diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/p/sys/vals/pos/pos_test.gno similarity index 99% rename from examples/gno.land/r/sys/vals/pos/pos_test.gno rename to examples/gno.land/p/sys/vals/pos/pos_test.gno index 3900a30c5a6..4ef66148965 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/p/sys/vals/pos/pos_test.gno @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set diff --git a/examples/gno.land/p/sys/vals/types/gno.mod b/examples/gno.land/p/sys/vals/types/gno.mod new file mode 100644 index 00000000000..f728025168c --- /dev/null +++ b/examples/gno.land/p/sys/vals/types/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sys/vals/types diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types/types.gno new file mode 100644 index 00000000000..ae3a5710e8f --- /dev/null +++ b/examples/gno.land/p/sys/vals/types/types.gno @@ -0,0 +1,50 @@ +package types + +import ( + "std" +) + +// Protocol defines the validator set protocol (PoA / PoS / PoC / ?) +type Protocol interface { + // AddValidator adds a new validator to the validator set. + // If the validator is already present, the method should error out + // + // TODO: This API is not ideal -- the address should be derived from + // the public key, and not be passed in as such, but currently Gno + // does not support crypto address derivation + AddValidator(address std.Address, pubKey string) *Validator + + // RemoveValidator removes the given validator from the set. + // If the validator is not present in the set, the method should error out + RemoveValidator(address std.Address) *Validator + + // IsValidator returns a flag indicating if the given + // bech32 address is part of the validator set + IsValidator(address std.Address) bool + + // GetValidators returns the currently active validator set + GetValidators() []*Validator +} + +// Validator represents a single chain validator +type Validator struct { + Address std.Address // bech32 address + PubKey string // bech32 representation of the public key + VotingPower uint64 +} + +const ( + ValidatorAddedEvent = "ValidatorAdded" // emitted when a validator was added to the set + ValidatorRemovedEvent = "ValidatorRemoved" // emitted when a validator was removed from the set +) + +const ( + // ErrCallerNotUserAccount is returned when the caller is not a user account + ErrCallerNotUserAccount = "caller not user account" + + // ErrValidatorExists is returned when the validator is already in the set + ErrValidatorExists = "validator already exists" + + // ErrValidatorMissing is returned when the validator is not in the set + ErrValidatorMissing = "validator doesn't exist" +) diff --git a/examples/gno.land/r/gov/integration/z_filetest.gno b/examples/gno.land/r/gov/integration/z_filetest.gno index 23669da7333..a85588e4f11 100644 --- a/examples/gno.land/r/gov/integration/z_filetest.gno +++ b/examples/gno.land/r/gov/integration/z_filetest.gno @@ -1,7 +1,7 @@ package main import ( - govdao "github.com/gnolang/gno/examples/gno.land/r/gov/dao" + govdao "gno.land/r/gov/dao" _ "gno.land/r/gov/proposals/prop1" ) diff --git a/examples/gno.land/r/gov/proposals/prop1/prop1.gno b/examples/gno.land/r/gov/proposals/prop1/prop1.gno index e71f211efb9..d01c6bbaf25 100644 --- a/examples/gno.land/r/gov/proposals/prop1/prop1.gno +++ b/examples/gno.land/r/gov/proposals/prop1/prop1.gno @@ -10,23 +10,38 @@ package prop1 import ( - "gno.land/p/gov/proposal" + "std" + + "gno.land/p/sys/vals/types" govdao "gno.land/r/gov/dao" "gno.land/r/sys/vals" ) func init() { // Create the validators change proposal. - changesFn := func() error { - vals.AddValidator("g12345678", "pubkey") // add a new validator - vals.RemoveValidator("g000000000") // remove an existing validator - - return nil + changesFn := func() []types.Validator { + return []types.Validator{ + { + Address: std.Address("g12345678"), + PubKey: "pubkey", + VotingPower: 10, // add a new validator + }, + { + Address: std.Address("g000000000"), + PubKey: "pubkey", + VotingPower: 10, // add a new validator + }, + { + Address: std.Address("g000000000"), + PubKey: "pubkey", + VotingPower: 0, // remove an existing validator + }, + } } // Wraps changesFn to emit a certified event only if executed from a // complete governance proposal process. - executor := proposal.NewExecutor(changesFn) + executor := vals.NewPropExecutor(changesFn) // Create a proposal. // XXX: payment diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index ccdc2ade1f9..a05471fdaab 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -1,6 +1,6 @@ module gno.land/r/sys/vals require ( - gno.land/r/sys/vals/poc v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest + gno.land/p/sys/vals/poc v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/poa/gno.mod b/examples/gno.land/r/sys/vals/poa/gno.mod deleted file mode 100644 index 18f43c698a6..00000000000 --- a/examples/gno.land/r/sys/vals/poa/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/vals/poa - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno new file mode 100644 index 00000000000..119472cac54 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -0,0 +1,117 @@ +package vals + +import ( + "std" + + "gno.land/p/gov/proposal" + "gno.land/p/sys/vals/poc" + "gno.land/p/sys/vals/types" +) + +const daoPkgPath = "gno.land/r/gov/dao" + +const ( + errNoChangesProposed = "no set changes proposed" + errNotGovDAO = "caller not govdao executor" +) + +// vals is the wrapper for the validator set protocol +type vals struct { + p types.Protocol // p is the underlying validator set protocol + changes []types.Validator // changes are the set changes that happened between scrapes +} + +// v holds the active on-chain validator set state +var v = &vals{ + p: poc.NewPoC(), + changes: make([]types.Validator, 0), +} + +// NewPropExecutor creates a new executor that wraps a changes closure +// proposal. This wrapper is required to ensure the GovDAO Realm actually +// executed the callback. +// +// Concept adapted from: +// https://github.com/gnolang/gno/pull/1945 +func NewPropExecutor(changesFn func() []types.Validator) proposal.Executor { + if changesFn == nil { + panic(errNoChangesProposed) + } + + callback := func() error { + // Make sure the GovDAO executor runs the valset changes + assertGovDAOCaller() + + for _, change := range changesFn() { + if change.VotingPower == 0 { + // This change request is to remove the validator + removeValidator(change.Address) + + continue + } + + // This change request is to add the validator + addValidator(change.Address, change.PubKey) + } + + return nil + } + + return proposal.NewExecutor(callback) +} + +// assertGovDAOCaller verifies the caller is the GovDAO executor +func assertGovDAOCaller() { + if std.PrevRealm().PkgPath() != daoPkgPath { + panic(errNotGovDAO) + } +} + +// addValidator adds a new validator to the validator set. +// If the validator is already present, the method errors out +func addValidator(address std.Address, pubKey string) { + if val := v.p.AddValidator(address, pubKey); val != nil { + // Validator added, note the change + v.changes = append(v.changes, types.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: val.VotingPower, + }) + } +} + +// removeValidator removes the given validator from the set. +// If the validator is not present in the set, the method errors out +func removeValidator(address std.Address) { + if val := v.p.RemoveValidator(address); val != nil { + // Validator removed, note the change + v.changes = append(v.changes, types.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: 0, // nullified the voting power indicates removal + }) + } +} + +// IsValidator returns a flag indicating if the given bech32 address +// is part of the validator set +func IsValidator(address string) bool { + return v.p.IsValidator(std.Address(address)) +} + +// GetValidators returns the typed validator set +func GetValidators() []*types.Validator { + return v.p.GetValidators() +} + +// getChanges returns the validator changes stored on the realm +func getChanges() []types.Validator { + // Construct the changes + changes := make([]types.Validator, len(v.changes)) + copy(changes, v.changes) + + // Reset the changes set + v.changes = v.changes[:0] + + return changes +} diff --git a/examples/gno.land/r/sys/vals/poc/gno.mod b/examples/gno.land/r/sys/vals/poc/gno.mod deleted file mode 100644 index 5ed131de978..00000000000 --- a/examples/gno.land/r/sys/vals/poc/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/vals/poc - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/vals/pos/gno.mod b/examples/gno.land/r/sys/vals/pos/gno.mod deleted file mode 100644 index 3270ff929ec..00000000000 --- a/examples/gno.land/r/sys/vals/pos/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/vals/pos - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/vals/types/gno.mod b/examples/gno.land/r/sys/vals/types/gno.mod deleted file mode 100644 index 32dd9f06413..00000000000 --- a/examples/gno.land/r/sys/vals/types/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/sys/vals/types diff --git a/examples/gno.land/r/sys/vals/types/types.gno b/examples/gno.land/r/sys/vals/types/types.gno deleted file mode 100644 index 6f2a5bc7d93..00000000000 --- a/examples/gno.land/r/sys/vals/types/types.gno +++ /dev/null @@ -1,28 +0,0 @@ -package types - -import ( - "std" -) - -// Validator represents a single chain validator -type Validator struct { - Address std.Address // bech32 address - PubKey string // bech32 representation of the public key - VotingPower uint64 -} - -const ( - ValidatorAddedEvent = "ValidatorAdded" // emitted when a validator was added to the set - ValidatorRemovedEvent = "ValidatorRemoved" // emitted when a validator was removed from the set -) - -const ( - // ErrCallerNotUserAccount is returned when the caller is not a user account - ErrCallerNotUserAccount = "caller not user account" - - // ErrValidatorExists is returned when the validator is already in the set - ErrValidatorExists = "validator already exists" - - // ErrValidatorMissing is returned when the validator is not in the set - ErrValidatorMissing = "validator doesn't exist" -) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno deleted file mode 100644 index c505ebf7864..00000000000 --- a/examples/gno.land/r/sys/vals/vals.gno +++ /dev/null @@ -1,91 +0,0 @@ -package vals - -import ( - "std" - - "gno.land/r/sys/vals/poc" - "gno.land/r/sys/vals/types" -) - -// Protocol defines the validator set protocol (PoA / PoS) -type Protocol interface { - // AddValidator adds a new validator to the validator set. - // If the validator is already present, the method should error out - // - // TODO: This API is not ideal -- the address should be derived from - // the public key, and not be passed in as such, but currently Gno - // does not support crypto address derivation - AddValidator(address std.Address, pubKey string) *types.Validator - - // RemoveValidator removes the given validator from the set. - // If the validator is not present in the set, the method should error out - RemoveValidator(address std.Address) *types.Validator - - // IsValidator returns a flag indicating if the given - // bech32 address is part of the validator set - IsValidator(address std.Address) bool - - // GetValidators returns the currently active validator set - GetValidators() []*types.Validator -} - -// vals is the wrapper for the validator set protocol -type vals struct { - p Protocol // p is the underlying validator set protocol (PoA / PoS) - changes []types.Validator // changes are the set changes that happened between scrapes -} - -// v holds the active on-chain validator set state -var v = &vals{ - p: poc.NewPoC(), // PoC by default - changes: make([]types.Validator, 0), -} - -// AddValidator adds a new validator to the validator set. -// If the validator is already present, the method errors out -func AddValidator(address, pubKey string) { - if val := v.p.AddValidator(std.Address(address), pubKey); val != nil { - // Validator added, note the change - v.changes = append(v.changes, types.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: val.VotingPower, - }) - } -} - -// RemoveValidator removes the given validator from the set. -// If the validator is not present in the set, the method errors out -func RemoveValidator(address string) { - if val := v.p.RemoveValidator(std.Address(address)); val != nil { - // Validator removed, note the change - v.changes = append(v.changes, types.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: 0, // nullified the voting power indicates removal - }) - } -} - -// IsValidator returns a flag indicating if the given bech32 address -// is part of the validator set -func IsValidator(address string) bool { - return v.p.IsValidator(std.Address(address)) -} - -// GetValidators returns the typed validator set -func GetValidators() []*types.Validator { - return v.p.GetValidators() -} - -// getChanges returns the validator changes stored on the realm -func getChanges() []types.Validator { - // Construct the changes - changes := make([]types.Validator, len(v.changes)) - copy(changes, v.changes) - - // Reset the changes set - v.changes = v.changes[:0] - - return changes -} From eb34c7ae5674e66d3219137db9a7471bab3b0c8e Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 17 Jun 2024 22:00:43 +0200 Subject: [PATCH 19/71] Tidy gno mod --- examples/gno.land/r/gov/proposals/prop1/gno.mod | 2 +- examples/gno.land/r/sys/vals/gno.mod | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/r/gov/proposals/prop1/gno.mod b/examples/gno.land/r/gov/proposals/prop1/gno.mod index 270ccc7b24c..d82147df398 100644 --- a/examples/gno.land/r/gov/proposals/prop1/gno.mod +++ b/examples/gno.land/r/gov/proposals/prop1/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/gov/proposals/prop1 require ( - gno.land/p/gov/proposal v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest gno.land/r/gov/dao v0.0.0-latest gno.land/r/sys/vals v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index a05471fdaab..221b2e3370c 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -1,6 +1,7 @@ module gno.land/r/sys/vals require ( + gno.land/p/gov/proposal v0.0.0-latest gno.land/p/sys/vals/poc v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) From a3049c3c04a6e1f5d748b75ce22f8f06bd2e00bd Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 17 Jun 2024 22:34:39 +0200 Subject: [PATCH 20/71] Move back to r/sys/vals --- examples/gno.land/p/sys/vals/poa/gno.mod | 6 ------ examples/gno.land/p/sys/vals/poc/gno.mod | 6 ------ examples/gno.land/p/sys/vals/pos/gno.mod | 6 ------ examples/gno.land/p/sys/vals/types/gno.mod | 1 - examples/gno.land/r/gov/proposals/prop1/gno.mod | 2 +- examples/gno.land/r/gov/proposals/prop1/prop1.gno | 2 +- examples/gno.land/r/sys/vals/gno.mod | 4 ++-- examples/gno.land/r/sys/vals/poa/gno.mod | 6 ++++++ examples/gno.land/{p => r}/sys/vals/poa/option.gno | 2 +- examples/gno.land/{p => r}/sys/vals/poa/poa.gno | 8 +++++--- examples/gno.land/{p => r}/sys/vals/poa/poa_test.gno | 10 +++++----- examples/gno.land/{p => r}/sys/vals/poa/vote.gno | 11 ++++++++--- examples/gno.land/r/sys/vals/poc.gno | 4 ++-- examples/gno.land/r/sys/vals/poc/gno.mod | 6 ++++++ examples/gno.land/{p => r}/sys/vals/poc/option.gno | 2 +- examples/gno.land/{p => r}/sys/vals/poc/poc.gno | 2 +- examples/gno.land/{p => r}/sys/vals/poc/poc_test.gno | 2 +- examples/gno.land/r/sys/vals/pos/gno.mod | 6 ++++++ examples/gno.land/{p => r}/sys/vals/pos/option.gno | 2 +- examples/gno.land/{p => r}/sys/vals/pos/pos.gno | 2 +- examples/gno.land/{p => r}/sys/vals/pos/pos_test.gno | 2 +- examples/gno.land/r/sys/vals/types/gno.mod | 1 + examples/gno.land/{p => r}/sys/vals/types/types.gno | 0 23 files changed, 50 insertions(+), 43 deletions(-) delete mode 100644 examples/gno.land/p/sys/vals/poa/gno.mod delete mode 100644 examples/gno.land/p/sys/vals/poc/gno.mod delete mode 100644 examples/gno.land/p/sys/vals/pos/gno.mod delete mode 100644 examples/gno.land/p/sys/vals/types/gno.mod create mode 100644 examples/gno.land/r/sys/vals/poa/gno.mod rename examples/gno.land/{p => r}/sys/vals/poa/option.gno (92%) rename examples/gno.land/{p => r}/sys/vals/poa/poa.gno (95%) rename examples/gno.land/{p => r}/sys/vals/poa/poa_test.gno (97%) rename examples/gno.land/{p => r}/sys/vals/poa/vote.gno (92%) create mode 100644 examples/gno.land/r/sys/vals/poc/gno.mod rename examples/gno.land/{p => r}/sys/vals/poc/option.gno (90%) rename examples/gno.land/{p => r}/sys/vals/poc/poc.gno (98%) rename examples/gno.land/{p => r}/sys/vals/poc/poc_test.gno (99%) create mode 100644 examples/gno.land/r/sys/vals/pos/gno.mod rename examples/gno.land/{p => r}/sys/vals/pos/option.gno (93%) rename examples/gno.land/{p => r}/sys/vals/pos/pos.gno (99%) rename examples/gno.land/{p => r}/sys/vals/pos/pos_test.gno (99%) create mode 100644 examples/gno.land/r/sys/vals/types/gno.mod rename examples/gno.land/{p => r}/sys/vals/types/types.gno (100%) diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod deleted file mode 100644 index de760cae626..00000000000 --- a/examples/gno.land/p/sys/vals/poa/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/sys/vals/poa - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod deleted file mode 100644 index 0263460b501..00000000000 --- a/examples/gno.land/p/sys/vals/poc/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/sys/vals/poc - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/p/sys/vals/pos/gno.mod b/examples/gno.land/p/sys/vals/pos/gno.mod deleted file mode 100644 index 585c7b6afc8..00000000000 --- a/examples/gno.land/p/sys/vals/pos/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/sys/vals/pos - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/p/sys/vals/types/gno.mod b/examples/gno.land/p/sys/vals/types/gno.mod deleted file mode 100644 index f728025168c..00000000000 --- a/examples/gno.land/p/sys/vals/types/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/sys/vals/types diff --git a/examples/gno.land/r/gov/proposals/prop1/gno.mod b/examples/gno.land/r/gov/proposals/prop1/gno.mod index d82147df398..82258833cb9 100644 --- a/examples/gno.land/r/gov/proposals/prop1/gno.mod +++ b/examples/gno.land/r/gov/proposals/prop1/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/gov/proposals/prop1 require ( - gno.land/p/sys/vals/types v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest gno.land/r/gov/dao v0.0.0-latest gno.land/r/sys/vals v0.0.0-latest ) diff --git a/examples/gno.land/r/gov/proposals/prop1/prop1.gno b/examples/gno.land/r/gov/proposals/prop1/prop1.gno index d01c6bbaf25..8a17d9038a5 100644 --- a/examples/gno.land/r/gov/proposals/prop1/prop1.gno +++ b/examples/gno.land/r/gov/proposals/prop1/prop1.gno @@ -12,9 +12,9 @@ package prop1 import ( "std" - "gno.land/p/sys/vals/types" govdao "gno.land/r/gov/dao" "gno.land/r/sys/vals" + "gno.land/r/sys/vals/types" ) func init() { diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index 221b2e3370c..db17a7006f4 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -2,6 +2,6 @@ module gno.land/r/sys/vals require ( gno.land/p/gov/proposal v0.0.0-latest - gno.land/p/sys/vals/poc v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest + gno.land/r/sys/vals/poc v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/poa/gno.mod b/examples/gno.land/r/sys/vals/poa/gno.mod new file mode 100644 index 00000000000..18f43c698a6 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poa/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals/poa + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/p/sys/vals/poa/option.gno b/examples/gno.land/r/sys/vals/poa/option.gno similarity index 92% rename from examples/gno.land/p/sys/vals/poa/option.gno rename to examples/gno.land/r/sys/vals/poa/option.gno index d551525daaf..8be3d115c48 100644 --- a/examples/gno.land/p/sys/vals/poa/option.gno +++ b/examples/gno.land/r/sys/vals/poa/option.gno @@ -1,6 +1,6 @@ package poa -import "gno.land/p/sys/vals/types" +import "gno.land/r/sys/vals/types" type Option func(*PoA) diff --git a/examples/gno.land/p/sys/vals/poa/poa.gno b/examples/gno.land/r/sys/vals/poa/poa.gno similarity index 95% rename from examples/gno.land/p/sys/vals/poa/poa.gno rename to examples/gno.land/r/sys/vals/poa/poa.gno index 54d43862ba0..67ef18d3122 100644 --- a/examples/gno.land/p/sys/vals/poa/poa.gno +++ b/examples/gno.land/r/sys/vals/poa/poa.gno @@ -3,7 +3,7 @@ package poa import ( "std" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) const ( @@ -59,7 +59,8 @@ func (p *PoA) AddValidator(address std.Address, pubKey string) *types.Validator p.validateAdd(caller, address) // Cast the vote - p.votes.castVote(caller, address, true) + voterPower := p.validators[p.addressToValidatorIndex[caller]].VotingPower + p.votes.castVote(caller, address, voterPower, true) // Emit the vote event std.Emit(UserVotedAddEvent) @@ -135,7 +136,8 @@ func (p *PoA) RemoveValidator(address std.Address) *types.Validator { p.validateRemove(caller, address) // Cast the vote - p.votes.castVote(caller, address, false) + voterPower := p.validators[p.addressToValidatorIndex[caller]].VotingPower + p.votes.castVote(caller, address, voterPower, false) // Emit the vote event std.Emit(UserVotedRemoveEvent) diff --git a/examples/gno.land/p/sys/vals/poa/poa_test.gno b/examples/gno.land/r/sys/vals/poa/poa_test.gno similarity index 97% rename from examples/gno.land/p/sys/vals/poa/poa_test.gno rename to examples/gno.land/r/sys/vals/poa/poa_test.gno index 536779d32c2..40eaefcc591 100644 --- a/examples/gno.land/p/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/r/sys/vals/poa/poa_test.gno @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) // generateTestValidators generates a dummy validator set @@ -230,7 +230,7 @@ func TestPoA_RemoveValidator(t *testing.T) { initialSet[0].Address = callerAddress initialSet[0].PubKey = callerKey - initialSet[0].VotingPower = 1 + initialSet[0].VotingPower = 10 proposedAddress := initialSet[1].Address @@ -247,16 +247,16 @@ func TestPoA_RemoveValidator(t *testing.T) { // Make sure the validator is removed if p.IsValidator(proposedAddress) { - t.Fatal("address is validator") + t.Fatal("address is still a validator") } // Make sure the total voting power is changed - if p.totalVotingPower != 1 { + if p.totalVotingPower != 10 { t.Fatal("invalid total voting power") } // Make sure the majority voting power is changed - if p.majorityPower != 0 { + if p.majorityPower != 6 { t.Fatal("invalid majority voting power") } }) diff --git a/examples/gno.land/p/sys/vals/poa/vote.gno b/examples/gno.land/r/sys/vals/poa/vote.gno similarity index 92% rename from examples/gno.land/p/sys/vals/poa/vote.gno rename to examples/gno.land/r/sys/vals/poa/vote.gno index 3a8ebbcc99a..566c556de2b 100644 --- a/examples/gno.land/p/sys/vals/poa/vote.gno +++ b/examples/gno.land/r/sys/vals/poa/vote.gno @@ -33,7 +33,12 @@ func newVotingSystem() *votingSystem { } // castVote casts a new vote to the given candidate -func (v *votingSystem) castVote(voter, candidate std.Address, toAdd bool) { +func (v *votingSystem) castVote( + voter, + candidate std.Address, + voterPower uint64, + toAdd bool, +) { // Construct a unique proposal key (voterAddr_candidateAddr) votedKey := ufmt.Sprintf( "%s_%s", @@ -57,9 +62,9 @@ func (v *votingSystem) castVote(voter, candidate std.Address, toAdd bool) { // Update the tally if toAdd { - t.toAdd++ + t.toAdd += voterPower } else { - t.toRemove++ + t.toRemove += voterPower } // Save the tally diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno index 119472cac54..8c1d561beff 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -4,8 +4,8 @@ import ( "std" "gno.land/p/gov/proposal" - "gno.land/p/sys/vals/poc" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/poc" + "gno.land/r/sys/vals/types" ) const daoPkgPath = "gno.land/r/gov/dao" diff --git a/examples/gno.land/r/sys/vals/poc/gno.mod b/examples/gno.land/r/sys/vals/poc/gno.mod new file mode 100644 index 00000000000..5ed131de978 --- /dev/null +++ b/examples/gno.land/r/sys/vals/poc/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals/poc + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/p/sys/vals/poc/option.gno b/examples/gno.land/r/sys/vals/poc/option.gno similarity index 90% rename from examples/gno.land/p/sys/vals/poc/option.gno rename to examples/gno.land/r/sys/vals/poc/option.gno index ceccc193e7d..a10dc8f0882 100644 --- a/examples/gno.land/p/sys/vals/poc/option.gno +++ b/examples/gno.land/r/sys/vals/poc/option.gno @@ -1,6 +1,6 @@ package poc -import "gno.land/p/sys/vals/types" +import "gno.land/r/sys/vals/types" type Option func(*PoC) diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/r/sys/vals/poc/poc.gno similarity index 98% rename from examples/gno.land/p/sys/vals/poc/poc.gno rename to examples/gno.land/r/sys/vals/poc/poc.gno index 9d1075e5d54..9a3ef76942f 100644 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ b/examples/gno.land/r/sys/vals/poc/poc.gno @@ -3,7 +3,7 @@ package poc import ( "std" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) const govdaoRealm = "gno.land/r/gov/dao" diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/r/sys/vals/poc/poc_test.gno similarity index 99% rename from examples/gno.land/p/sys/vals/poc/poc_test.gno rename to examples/gno.land/r/sys/vals/poc/poc_test.gno index d2e2345a299..83f2686b042 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/r/sys/vals/poc/poc_test.gno @@ -6,7 +6,7 @@ import ( "std" "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) // generateTestValidators generates a dummy validator set diff --git a/examples/gno.land/r/sys/vals/pos/gno.mod b/examples/gno.land/r/sys/vals/pos/gno.mod new file mode 100644 index 00000000000..3270ff929ec --- /dev/null +++ b/examples/gno.land/r/sys/vals/pos/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/sys/vals/pos + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/p/sys/vals/pos/option.gno b/examples/gno.land/r/sys/vals/pos/option.gno similarity index 93% rename from examples/gno.land/p/sys/vals/pos/option.gno rename to examples/gno.land/r/sys/vals/pos/option.gno index a179555f79d..79602bac232 100644 --- a/examples/gno.land/p/sys/vals/pos/option.gno +++ b/examples/gno.land/r/sys/vals/pos/option.gno @@ -3,7 +3,7 @@ package pos import ( "std" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) type Option func(*PoS) diff --git a/examples/gno.land/p/sys/vals/pos/pos.gno b/examples/gno.land/r/sys/vals/pos/pos.gno similarity index 99% rename from examples/gno.land/p/sys/vals/pos/pos.gno rename to examples/gno.land/r/sys/vals/pos/pos.gno index a8caec55ea5..b48710142fb 100644 --- a/examples/gno.land/p/sys/vals/pos/pos.gno +++ b/examples/gno.land/r/sys/vals/pos/pos.gno @@ -3,7 +3,7 @@ package pos import ( "std" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) var errValidatorNotOrigin = "validator address is not origin" diff --git a/examples/gno.land/p/sys/vals/pos/pos_test.gno b/examples/gno.land/r/sys/vals/pos/pos_test.gno similarity index 99% rename from examples/gno.land/p/sys/vals/pos/pos_test.gno rename to examples/gno.land/r/sys/vals/pos/pos_test.gno index 4ef66148965..3900a30c5a6 100644 --- a/examples/gno.land/p/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/r/sys/vals/pos/pos_test.gno @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/types" + "gno.land/r/sys/vals/types" ) // generateTestValidators generates a dummy validator set diff --git a/examples/gno.land/r/sys/vals/types/gno.mod b/examples/gno.land/r/sys/vals/types/gno.mod new file mode 100644 index 00000000000..32dd9f06413 --- /dev/null +++ b/examples/gno.land/r/sys/vals/types/gno.mod @@ -0,0 +1 @@ +module gno.land/r/sys/vals/types diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/r/sys/vals/types/types.gno similarity index 100% rename from examples/gno.land/p/sys/vals/types/types.gno rename to examples/gno.land/r/sys/vals/types/types.gno From cfd900d2d9a094f5d4f36b4f4ef7711dbee2a5d6 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 17 Jun 2024 22:37:58 +0200 Subject: [PATCH 21/71] Tidy gno mod --- examples/gno.land/r/gov/proposals/prop1/gno.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/gov/proposals/prop1/gno.mod b/examples/gno.land/r/gov/proposals/prop1/gno.mod index 82258833cb9..1c840d9d398 100644 --- a/examples/gno.land/r/gov/proposals/prop1/gno.mod +++ b/examples/gno.land/r/gov/proposals/prop1/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/gov/proposals/prop1 require ( - gno.land/r/sys/vals/types v0.0.0-latest gno.land/r/gov/dao v0.0.0-latest gno.land/r/sys/vals v0.0.0-latest + gno.land/r/sys/vals/types v0.0.0-latest ) From f1151be912c5897f5eb7ddf6c38cf49838cb55d7 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 18 Jun 2024 09:53:55 +0200 Subject: [PATCH 22/71] Tidy --- examples/gno.land/p/sys/vals/poa/gno.mod | 6 ++ .../gno.land/{r => p}/sys/vals/poa/option.gno | 2 +- .../gno.land/{r => p}/sys/vals/poa/poa.gno | 8 +-- .../{r => p}/sys/vals/poa/poa_test.gno | 2 +- .../gno.land/{r => p}/sys/vals/poa/vote.gno | 0 examples/gno.land/p/sys/vals/poc/gno.mod | 6 ++ .../gno.land/{r => p}/sys/vals/poc/option.gno | 2 +- .../gno.land/{r => p}/sys/vals/poc/poc.gno | 21 +------ .../{r => p}/sys/vals/poc/poc_test.gno | 51 +--------------- examples/gno.land/p/sys/vals/pos/gno.mod | 6 ++ .../gno.land/{r => p}/sys/vals/pos/option.gno | 6 +- .../gno.land/{r => p}/sys/vals/pos/pos.gno | 8 ++- .../{r => p}/sys/vals/pos/pos_test.gno | 14 +---- examples/gno.land/p/sys/vals/types/gno.mod | 1 + .../{r => p}/sys/vals/types/types.gno | 0 .../gno.land/r/gov/proposals/prop1/gno.mod | 2 +- .../gno.land/r/gov/proposals/prop1/prop1.gno | 2 +- examples/gno.land/r/sys/vals/doc.gno | 3 + examples/gno.land/r/sys/vals/gno.mod | 4 +- examples/gno.land/r/sys/vals/init.gno | 13 ++++ examples/gno.land/r/sys/vals/poa/gno.mod | 6 -- examples/gno.land/r/sys/vals/poc.gno | 53 +--------------- examples/gno.land/r/sys/vals/poc/gno.mod | 6 -- examples/gno.land/r/sys/vals/pos/gno.mod | 6 -- examples/gno.land/r/sys/vals/types/gno.mod | 1 - examples/gno.land/r/sys/vals/vals.gno | 60 +++++++++++++++++++ 26 files changed, 119 insertions(+), 170 deletions(-) create mode 100644 examples/gno.land/p/sys/vals/poa/gno.mod rename examples/gno.land/{r => p}/sys/vals/poa/option.gno (92%) rename examples/gno.land/{r => p}/sys/vals/poa/poa.gno (97%) rename examples/gno.land/{r => p}/sys/vals/poa/poa_test.gno (99%) rename examples/gno.land/{r => p}/sys/vals/poa/vote.gno (100%) create mode 100644 examples/gno.land/p/sys/vals/poc/gno.mod rename examples/gno.land/{r => p}/sys/vals/poc/option.gno (90%) rename examples/gno.land/{r => p}/sys/vals/poc/poc.gno (83%) rename examples/gno.land/{r => p}/sys/vals/poc/poc_test.gno (70%) create mode 100644 examples/gno.land/p/sys/vals/pos/gno.mod rename examples/gno.land/{r => p}/sys/vals/pos/option.gno (73%) rename examples/gno.land/{r => p}/sys/vals/pos/pos.gno (97%) rename examples/gno.land/{r => p}/sys/vals/pos/pos_test.gno (95%) create mode 100644 examples/gno.land/p/sys/vals/types/gno.mod rename examples/gno.land/{r => p}/sys/vals/types/types.gno (100%) create mode 100644 examples/gno.land/r/sys/vals/doc.gno create mode 100644 examples/gno.land/r/sys/vals/init.gno delete mode 100644 examples/gno.land/r/sys/vals/poa/gno.mod delete mode 100644 examples/gno.land/r/sys/vals/poc/gno.mod delete mode 100644 examples/gno.land/r/sys/vals/pos/gno.mod delete mode 100644 examples/gno.land/r/sys/vals/types/gno.mod create mode 100644 examples/gno.land/r/sys/vals/vals.gno diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod new file mode 100644 index 00000000000..de760cae626 --- /dev/null +++ b/examples/gno.land/p/sys/vals/poa/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/sys/vals/poa + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/poa/option.gno b/examples/gno.land/p/sys/vals/poa/option.gno similarity index 92% rename from examples/gno.land/r/sys/vals/poa/option.gno rename to examples/gno.land/p/sys/vals/poa/option.gno index 8be3d115c48..d551525daaf 100644 --- a/examples/gno.land/r/sys/vals/poa/option.gno +++ b/examples/gno.land/p/sys/vals/poa/option.gno @@ -1,6 +1,6 @@ package poa -import "gno.land/r/sys/vals/types" +import "gno.land/p/sys/vals/types" type Option func(*PoA) diff --git a/examples/gno.land/r/sys/vals/poa/poa.gno b/examples/gno.land/p/sys/vals/poa/poa.gno similarity index 97% rename from examples/gno.land/r/sys/vals/poa/poa.gno rename to examples/gno.land/p/sys/vals/poa/poa.gno index 67ef18d3122..45162a728ed 100644 --- a/examples/gno.land/r/sys/vals/poa/poa.gno +++ b/examples/gno.land/p/sys/vals/poa/poa.gno @@ -3,7 +3,7 @@ package poa import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) const ( @@ -113,9 +113,6 @@ func (p *PoA) addValidator(address std.Address, pubKey string) *types.Validator p.totalVotingPower += v.VotingPower p.majorityPower = (2 * p.totalVotingPower) / 3 - // Emit the validator set change - std.Emit(types.ValidatorAddedEvent) - // Remove the candidate from the voting system p.votes.resetCandidate(v.Address) @@ -189,9 +186,6 @@ func (p *PoA) removeValidator(address std.Address) *types.Validator { p.totalVotingPower -= validator.VotingPower p.majorityPower = (2 * p.totalVotingPower) / 3 - // Emit the validator set change - std.Emit(types.ValidatorRemovedEvent) - // Remove the candidate from the voting system p.votes.resetCandidate(address) diff --git a/examples/gno.land/r/sys/vals/poa/poa_test.gno b/examples/gno.land/p/sys/vals/poa/poa_test.gno similarity index 99% rename from examples/gno.land/r/sys/vals/poa/poa_test.gno rename to examples/gno.land/p/sys/vals/poa/poa_test.gno index 40eaefcc591..bb0bbfd51b3 100644 --- a/examples/gno.land/r/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/p/sys/vals/poa/poa_test.gno @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set diff --git a/examples/gno.land/r/sys/vals/poa/vote.gno b/examples/gno.land/p/sys/vals/poa/vote.gno similarity index 100% rename from examples/gno.land/r/sys/vals/poa/vote.gno rename to examples/gno.land/p/sys/vals/poa/vote.gno diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod new file mode 100644 index 00000000000..0263460b501 --- /dev/null +++ b/examples/gno.land/p/sys/vals/poc/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/sys/vals/poc + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/poc/option.gno b/examples/gno.land/p/sys/vals/poc/option.gno similarity index 90% rename from examples/gno.land/r/sys/vals/poc/option.gno rename to examples/gno.land/p/sys/vals/poc/option.gno index a10dc8f0882..ceccc193e7d 100644 --- a/examples/gno.land/r/sys/vals/poc/option.gno +++ b/examples/gno.land/p/sys/vals/poc/option.gno @@ -1,6 +1,6 @@ package poc -import "gno.land/r/sys/vals/types" +import "gno.land/p/sys/vals/types" type Option func(*PoC) diff --git a/examples/gno.land/r/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno similarity index 83% rename from examples/gno.land/r/sys/vals/poc/poc.gno rename to examples/gno.land/p/sys/vals/poc/poc.gno index 9a3ef76942f..2519cc1acd1 100644 --- a/examples/gno.land/r/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -3,13 +3,9 @@ package poc import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) -const govdaoRealm = "gno.land/r/gov/dao" - -const errNotGovdao = "call not executed by govdao" - // PoC specifies the Proof of Contribution validator set. // In order to become part of the set, users to be voted into // the validator set by a govdao proposal @@ -51,18 +47,11 @@ func (p *PoC) AddValidator(address std.Address, pubKey string) *types.Validator p.addressToValidatorIndex[v.Address] = len(p.validators) p.validators = append(p.validators, v) - // Emit the validator set change - std.Emit(types.ValidatorAddedEvent) - return v } // validateAdd validates a validator add call func (p *PoC) validateAdd(address std.Address) { - if std.PrevRealm().PkgPath() != govdaoRealm { - panic(errNotGovdao) - } - // Check if the validator is already in the set if p.IsValidator(address) { panic(types.ErrValidatorExists) @@ -82,19 +71,11 @@ func (p *PoC) RemoveValidator(address std.Address) *types.Validator { delete(p.addressToValidatorIndex, address) - // Emit the validator set change - std.Emit(types.ValidatorRemovedEvent) - return validator } // validateRemove validates a validator remove call func (p *PoC) validateRemove(address std.Address) { - // Check if the method call is from a proposal execution - if std.PrevRealm().PkgPath() != govdaoRealm { - panic(errNotGovdao) - } - // Check if the address is a validator if !p.IsValidator(address) { panic(types.ErrValidatorMissing) diff --git a/examples/gno.land/r/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno similarity index 70% rename from examples/gno.land/r/sys/vals/poc/poc_test.gno rename to examples/gno.land/p/sys/vals/poc/poc_test.gno index 83f2686b042..a6b63e97dc0 100644 --- a/examples/gno.land/r/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -6,7 +6,7 @@ import ( "std" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set @@ -29,32 +29,9 @@ func generateTestValidators(count int) []*types.Validator { func TestPoC_AddValidator_Invalid(t *testing.T) { t.Parallel() - t.Run("call from realm != govdao", func(t *testing.T) { - t.Parallel() - - r := std.NewCodeRealm("gno.land/r/gov/randomdao") - std.TestSetRealm(r) - - var ( - proposalAddress = std.Address("caller") - proposalKey = "public-key" - ) - - // Create the protocol with no initial set - p := NewPoC() - - // Attempt to add the validator - testing.PanicsWithError(t, errNotGovdao, func() { - p.AddValidator(proposalAddress, proposalKey) - }) - }) - t.Run("validator already in set", func(t *testing.T) { t.Parallel() - r := std.NewCodeRealm(govdaoRealm) - std.TestSetRealm(r) - var ( proposalAddress = std.Address("caller") proposalKey = "public-key" @@ -78,9 +55,6 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { func TestPoC_AddValidator(t *testing.T) { t.Parallel() - r := std.NewCodeRealm(govdaoRealm) - std.TestSetRealm(r) - var ( proposalAddress = std.Address("caller") proposalKey = "public-key" @@ -103,29 +77,9 @@ func TestPoC_AddValidator(t *testing.T) { func TestPoC_RemoveValidator_Invalid(t *testing.T) { t.Parallel() - t.Run("call from realm != govdao", func(t *testing.T) { - t.Parallel() - - r := std.NewCodeRealm("gno.land/r/gov/randomdao") - std.TestSetRealm(r) - - proposalAddress := std.Address("caller") - - // Create the protocol with no initial set - p := NewPoC() - - // Attempt to add the validator - testing.PanicsWithError(t, errNotGovdao, func() { - p.RemoveValidator(proposalAddress) - }) - }) - t.Run("proposed removal not in set", func(t *testing.T) { t.Parallel() - r := std.NewCodeRealm(govdaoRealm) - std.TestSetRealm(r) - var ( proposalAddress = std.Address("caller") initialSet = generateTestValidators(1) @@ -146,9 +100,6 @@ func TestPoC_RemoveValidator_Invalid(t *testing.T) { func TestPoC_RemoveValidator(t *testing.T) { t.Parallel() - r := std.NewCodeRealm(govdaoRealm) - std.TestSetRealm(r) - var ( proposalAddress = std.Address("caller") initialSet = generateTestValidators(1) diff --git a/examples/gno.land/p/sys/vals/pos/gno.mod b/examples/gno.land/p/sys/vals/pos/gno.mod new file mode 100644 index 00000000000..585c7b6afc8 --- /dev/null +++ b/examples/gno.land/p/sys/vals/pos/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/sys/vals/pos + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest +) diff --git a/examples/gno.land/r/sys/vals/pos/option.gno b/examples/gno.land/p/sys/vals/pos/option.gno similarity index 73% rename from examples/gno.land/r/sys/vals/pos/option.gno rename to examples/gno.land/p/sys/vals/pos/option.gno index 79602bac232..6844734dfde 100644 --- a/examples/gno.land/r/sys/vals/pos/option.gno +++ b/examples/gno.land/p/sys/vals/pos/option.gno @@ -3,9 +3,11 @@ package pos import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) +var initialStake = std.Coin{"ugnot", 1000} + type Option func(*PoS) // WithInitialSet sets the initial PoS validator set, with empty stakes @@ -14,7 +16,7 @@ func WithInitialSet(validators []*types.Validator) Option { for index, validator := range validators { p.validators = append(p.validators, validator) p.addressToValidatorIndex[validator.Address] = index - p.addressToStakedAmount[validator.Address] = std.Coin{"ugnot", 0} // no stake + p.addressToStakedAmount[validator.Address] = initialStake } } } diff --git a/examples/gno.land/r/sys/vals/pos/pos.gno b/examples/gno.land/p/sys/vals/pos/pos.gno similarity index 97% rename from examples/gno.land/r/sys/vals/pos/pos.gno rename to examples/gno.land/p/sys/vals/pos/pos.gno index b48710142fb..dcdd0e2b594 100644 --- a/examples/gno.land/r/sys/vals/pos/pos.gno +++ b/examples/gno.land/p/sys/vals/pos/pos.gno @@ -3,7 +3,7 @@ package pos import ( "std" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) var errValidatorNotOrigin = "validator address is not origin" @@ -90,6 +90,9 @@ func (p *PoS) AddValidator(address std.Address, pubKey string) *types.Validator p.addressToValidatorIndex[caller] = len(p.validators) p.validators = append(p.validators, v) + // Rebalance the voting power + p.rebalanceVotingPower() + // Emit the validator set change std.Emit(types.ValidatorAddedEvent) @@ -178,6 +181,9 @@ func (p *PoS) RemoveValidator(address std.Address) *types.Validator { // Emit the unstake event std.Emit(UserUnstakedEvent) + // Rebalance the voting power + p.rebalanceVotingPower() + return validator } diff --git a/examples/gno.land/r/sys/vals/pos/pos_test.gno b/examples/gno.land/p/sys/vals/pos/pos_test.gno similarity index 95% rename from examples/gno.land/r/sys/vals/pos/pos_test.gno rename to examples/gno.land/p/sys/vals/pos/pos_test.gno index 3900a30c5a6..bdb6e558c31 100644 --- a/examples/gno.land/r/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/p/sys/vals/pos/pos_test.gno @@ -5,7 +5,7 @@ import ( "testing" "gno.land/p/demo/ufmt" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set @@ -272,7 +272,7 @@ func TestPoS_RemoveValidator(t *testing.T) { t.Parallel() var ( - callerAddress = std.Address("caller") + callerAddress = std.Address("g1d4y807day8r5z54a54rkjheq3l50l5e0xmceem") callerKey = "public-key" value = std.Coins{{"ugnot", stakeThreshold.Amount}} @@ -309,14 +309,4 @@ func TestPoS_RemoveValidator(t *testing.T) { if p.IsValidator(callerAddress) { t.Fatalf("should not be a validator") } - - // Make sure the balance is updated - var ( - banker = std.GetBanker(std.BankerTypeReadonly) - balance = banker.GetCoins(callerAddress).AmountOf("ugnot") - ) - - if balance != stakeThreshold.Amount { - t.Fatalf("returned balance mismatch") - } } diff --git a/examples/gno.land/p/sys/vals/types/gno.mod b/examples/gno.land/p/sys/vals/types/gno.mod new file mode 100644 index 00000000000..f728025168c --- /dev/null +++ b/examples/gno.land/p/sys/vals/types/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sys/vals/types diff --git a/examples/gno.land/r/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types/types.gno similarity index 100% rename from examples/gno.land/r/sys/vals/types/types.gno rename to examples/gno.land/p/sys/vals/types/types.gno diff --git a/examples/gno.land/r/gov/proposals/prop1/gno.mod b/examples/gno.land/r/gov/proposals/prop1/gno.mod index 1c840d9d398..27eda11251a 100644 --- a/examples/gno.land/r/gov/proposals/prop1/gno.mod +++ b/examples/gno.land/r/gov/proposals/prop1/gno.mod @@ -3,5 +3,5 @@ module gno.land/r/gov/proposals/prop1 require ( gno.land/r/gov/dao v0.0.0-latest gno.land/r/sys/vals v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/gov/proposals/prop1/prop1.gno b/examples/gno.land/r/gov/proposals/prop1/prop1.gno index 8a17d9038a5..d01c6bbaf25 100644 --- a/examples/gno.land/r/gov/proposals/prop1/prop1.gno +++ b/examples/gno.land/r/gov/proposals/prop1/prop1.gno @@ -12,9 +12,9 @@ package prop1 import ( "std" + "gno.land/p/sys/vals/types" govdao "gno.land/r/gov/dao" "gno.land/r/sys/vals" - "gno.land/r/sys/vals/types" ) func init() { diff --git a/examples/gno.land/r/sys/vals/doc.gno b/examples/gno.land/r/sys/vals/doc.gno new file mode 100644 index 00000000000..75405923376 --- /dev/null +++ b/examples/gno.land/r/sys/vals/doc.gno @@ -0,0 +1,3 @@ +// Package vals implements the on-chain validator set management through Proof of Contribution. +// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. +package vals diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index db17a7006f4..221b2e3370c 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -2,6 +2,6 @@ module gno.land/r/sys/vals require ( gno.land/p/gov/proposal v0.0.0-latest - gno.land/r/sys/vals/poc v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest + gno.land/p/sys/vals/poc v0.0.0-latest + gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/init.gno b/examples/gno.land/r/sys/vals/init.gno new file mode 100644 index 00000000000..5da5ad3d736 --- /dev/null +++ b/examples/gno.land/r/sys/vals/init.gno @@ -0,0 +1,13 @@ +package vals + +import ( + "gno.land/p/sys/vals/poc" + "gno.land/p/sys/vals/types" +) + +func init() { + v = &vals{ + p: poc.NewPoC(), + changes: make([]types.Validator, 0), + } +} diff --git a/examples/gno.land/r/sys/vals/poa/gno.mod b/examples/gno.land/r/sys/vals/poa/gno.mod deleted file mode 100644 index 18f43c698a6..00000000000 --- a/examples/gno.land/r/sys/vals/poa/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/vals/poa - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno index 8c1d561beff..736903695b2 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -4,8 +4,7 @@ import ( "std" "gno.land/p/gov/proposal" - "gno.land/r/sys/vals/poc" - "gno.land/r/sys/vals/types" + "gno.land/p/sys/vals/types" ) const daoPkgPath = "gno.land/r/gov/dao" @@ -15,18 +14,6 @@ const ( errNotGovDAO = "caller not govdao executor" ) -// vals is the wrapper for the validator set protocol -type vals struct { - p types.Protocol // p is the underlying validator set protocol - changes []types.Validator // changes are the set changes that happened between scrapes -} - -// v holds the active on-chain validator set state -var v = &vals{ - p: poc.NewPoC(), - changes: make([]types.Validator, 0), -} - // NewPropExecutor creates a new executor that wraps a changes closure // proposal. This wrapper is required to ensure the GovDAO Realm actually // executed the callback. @@ -67,32 +54,6 @@ func assertGovDAOCaller() { } } -// addValidator adds a new validator to the validator set. -// If the validator is already present, the method errors out -func addValidator(address std.Address, pubKey string) { - if val := v.p.AddValidator(address, pubKey); val != nil { - // Validator added, note the change - v.changes = append(v.changes, types.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: val.VotingPower, - }) - } -} - -// removeValidator removes the given validator from the set. -// If the validator is not present in the set, the method errors out -func removeValidator(address std.Address) { - if val := v.p.RemoveValidator(address); val != nil { - // Validator removed, note the change - v.changes = append(v.changes, types.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: 0, // nullified the voting power indicates removal - }) - } -} - // IsValidator returns a flag indicating if the given bech32 address // is part of the validator set func IsValidator(address string) bool { @@ -103,15 +64,3 @@ func IsValidator(address string) bool { func GetValidators() []*types.Validator { return v.p.GetValidators() } - -// getChanges returns the validator changes stored on the realm -func getChanges() []types.Validator { - // Construct the changes - changes := make([]types.Validator, len(v.changes)) - copy(changes, v.changes) - - // Reset the changes set - v.changes = v.changes[:0] - - return changes -} diff --git a/examples/gno.land/r/sys/vals/poc/gno.mod b/examples/gno.land/r/sys/vals/poc/gno.mod deleted file mode 100644 index 5ed131de978..00000000000 --- a/examples/gno.land/r/sys/vals/poc/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/vals/poc - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/vals/pos/gno.mod b/examples/gno.land/r/sys/vals/pos/gno.mod deleted file mode 100644 index 3270ff929ec..00000000000 --- a/examples/gno.land/r/sys/vals/pos/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/sys/vals/pos - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/r/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/r/sys/vals/types/gno.mod b/examples/gno.land/r/sys/vals/types/gno.mod deleted file mode 100644 index 32dd9f06413..00000000000 --- a/examples/gno.land/r/sys/vals/types/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/sys/vals/types diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno new file mode 100644 index 00000000000..35661bc8911 --- /dev/null +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -0,0 +1,60 @@ +package vals + +import ( + "std" + + "gno.land/p/sys/vals/types" +) + +// vals is the wrapper for the validator set protocol +type vals struct { + p types.Protocol // p is the underlying validator set protocol + changes []types.Validator // changes are the set changes that happened between scrapes +} + +// v holds the active on-chain validator set state +var v *vals + +// addValidator adds a new validator to the validator set. +// If the validator is already present, the method errors out +func addValidator(address std.Address, pubKey string) { + if val := v.p.AddValidator(address, pubKey); val != nil { + // Validator added, note the change + v.changes = append(v.changes, types.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: val.VotingPower, + }) + + // Emit the validator set change + std.Emit(types.ValidatorAddedEvent) + } +} + +// removeValidator removes the given validator from the set. +// If the validator is not present in the set, the method errors out +func removeValidator(address std.Address) { + if val := v.p.RemoveValidator(address); val != nil { + // Validator removed, note the change + v.changes = append(v.changes, types.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: 0, // nullified the voting power indicates removal + }) + + // Emit the validator set change + std.Emit(types.ValidatorRemovedEvent) + } +} + +// getChanges returns the validator changes stored on the realm +func getChanges() []types.Validator { + // Construct the changes + changes := make([]types.Validator, len(v.changes)) + copy(changes, v.changes) + + // Reset the changes set + v.changes = v.changes[:0] + + return changes +} From 32fc7061569fcc5a0d1c1039b2efc29c6adb419a Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 18 Jun 2024 11:25:00 +0200 Subject: [PATCH 23/71] Tidy --- examples/gno.land/r/gov/proposals/prop1/gno.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/gov/proposals/prop1/gno.mod b/examples/gno.land/r/gov/proposals/prop1/gno.mod index 27eda11251a..d82147df398 100644 --- a/examples/gno.land/r/gov/proposals/prop1/gno.mod +++ b/examples/gno.land/r/gov/proposals/prop1/gno.mod @@ -1,7 +1,7 @@ module gno.land/r/gov/proposals/prop1 require ( + gno.land/p/sys/vals/types v0.0.0-latest gno.land/r/gov/dao v0.0.0-latest gno.land/r/sys/vals v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest ) From 2be75279b4f604a4d84463779cbe9250f106e544 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 18 Jun 2024 18:58:58 +0200 Subject: [PATCH 24/71] Move testing helpers to uassert --- examples/gno.land/p/demo/uassert/gno.mod | 1 + examples/gno.land/p/demo/uassert/uassert.gno | 64 +++++++++++++++++++ examples/gno.land/p/sys/vals/poa/gno.mod | 1 + examples/gno.land/p/sys/vals/poa/poa_test.gno | 17 ++--- examples/gno.land/p/sys/vals/poc/gno.mod | 1 + examples/gno.land/p/sys/vals/poc/poc_test.gno | 9 +-- examples/gno.land/p/sys/vals/pos/gno.mod | 1 + examples/gno.land/p/sys/vals/pos/pos_test.gno | 17 ++--- gnovm/stdlibs/testing/testing.gno | 61 ------------------ 9 files changed, 91 insertions(+), 81 deletions(-) create mode 100644 examples/gno.land/p/demo/uassert/gno.mod create mode 100644 examples/gno.land/p/demo/uassert/uassert.gno diff --git a/examples/gno.land/p/demo/uassert/gno.mod b/examples/gno.land/p/demo/uassert/gno.mod new file mode 100644 index 00000000000..a70e7db825d --- /dev/null +++ b/examples/gno.land/p/demo/uassert/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/uassert diff --git a/examples/gno.land/p/demo/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno new file mode 100644 index 00000000000..dac4d3a5ed4 --- /dev/null +++ b/examples/gno.land/p/demo/uassert/uassert.gno @@ -0,0 +1,64 @@ +package uassert + +import "testing" + +func extractPanicErr(r interface{}) string { + err, ok := r.(error) + if ok { + return err.Error() + } + + errStr, ok := r.(string) + if ok { + return errStr + } + + return "unknown error" +} + +// PanicsWithError asserts that the code inside the specified func panics, +// and that the recovered panic value is an error that satisfies the given message +func PanicsWithError(t *testing.T, errString string, f func()) bool { + t.Helper() + + var valid bool + + defer func() { + if r := recover(); r != nil { + // Check if the error matches + panicErr := extractPanicErr(r) + if panicErr == errString { + valid = true + + return + } + + t.Fatalf("Function panicked with err, %s", panicErr) + } + }() + + // Run the callback + f() + + return valid +} + +// NotPanics asserts that the code inside the specified func does NOT panic +func NotPanics(t *testing.T, f func()) bool { + t.Helper() + + valid := true + + defer func() { + if r := recover(); r != nil { + valid = false + + t.Fatalf("Function panicked with err, %s", extractPanicErr(r)) + } + }() + + // Run the callback + f() + + return valid +} diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod index de760cae626..a7439a81367 100644 --- a/examples/gno.land/p/sys/vals/poa/gno.mod +++ b/examples/gno.land/p/sys/vals/poa/gno.mod @@ -1,6 +1,7 @@ module gno.land/p/sys/vals/poa require ( + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/poa/poa_test.gno b/examples/gno.land/p/sys/vals/poa/poa_test.gno index bb0bbfd51b3..c6e5a7d64ea 100644 --- a/examples/gno.land/p/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/p/sys/vals/poa/poa_test.gno @@ -4,6 +4,7 @@ import ( "std" "testing" + "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" ) @@ -48,7 +49,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorExists, func() { + uassert.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -68,7 +69,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -101,7 +102,7 @@ func TestPoA_AddValidator(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.AddValidator(proposedAddress, proposedKey) }) @@ -150,7 +151,7 @@ func TestPoA_AddValidator(t *testing.T) { std.TestSetOrigCaller(validator.Address) // Attempt to add the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.AddValidator(proposedAddress, proposedKey) }) } @@ -187,7 +188,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, errCallerNotValidator, func() { + uassert.PanicsWithError(t, errCallerNotValidator, func() { p.RemoveValidator(callerAddress) }) }) @@ -209,7 +210,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(std.Address("totally random")) }) }) @@ -241,7 +242,7 @@ func TestPoA_RemoveValidator(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.RemoveValidator(proposedAddress) }) @@ -287,7 +288,7 @@ func TestPoA_RemoveValidator(t *testing.T) { std.TestSetOrigCaller(validator.Address) // Attempt to remove the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.RemoveValidator(proposedAddress) }) } diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod index 0263460b501..ad8e2df0533 100644 --- a/examples/gno.land/p/sys/vals/poc/gno.mod +++ b/examples/gno.land/p/sys/vals/poc/gno.mod @@ -1,6 +1,7 @@ module gno.land/p/sys/vals/poc require ( + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno index a6b63e97dc0..96ee2aae14d 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -5,6 +5,7 @@ import ( "std" + "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" ) @@ -46,7 +47,7 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { p := NewPoC(WithInitialSet(initialSet)) // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorExists, func() { + uassert.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(proposalAddress, proposalKey) }) }) @@ -64,7 +65,7 @@ func TestPoC_AddValidator(t *testing.T) { p := NewPoC() // Attempt to add the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.AddValidator(proposalAddress, proposalKey) }) @@ -91,7 +92,7 @@ func TestPoC_RemoveValidator_Invalid(t *testing.T) { p := NewPoC(WithInitialSet(initialSet)) // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(std.Address("totally random")) }) }) @@ -111,7 +112,7 @@ func TestPoC_RemoveValidator(t *testing.T) { p := NewPoC(WithInitialSet(initialSet)) // Attempt to remove the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.RemoveValidator(proposalAddress) }) diff --git a/examples/gno.land/p/sys/vals/pos/gno.mod b/examples/gno.land/p/sys/vals/pos/gno.mod index 585c7b6afc8..bab923d0d4f 100644 --- a/examples/gno.land/p/sys/vals/pos/gno.mod +++ b/examples/gno.land/p/sys/vals/pos/gno.mod @@ -1,6 +1,7 @@ module gno.land/p/sys/vals/pos require ( + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/pos/pos_test.gno b/examples/gno.land/p/sys/vals/pos/pos_test.gno index bdb6e558c31..4f5349a658b 100644 --- a/examples/gno.land/p/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/p/sys/vals/pos/pos_test.gno @@ -4,6 +4,7 @@ import ( "std" "testing" + "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" ) @@ -48,7 +49,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorExists, func() { + uassert.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -70,7 +71,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(std.Address("random address")) // Attempt to add the validator - testing.PanicsWithError(t, errValidatorNotOrigin, func() { + uassert.PanicsWithError(t, errValidatorNotOrigin, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -104,7 +105,7 @@ func TestPoS_AddValidator(t *testing.T) { } // Attempt to add the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.AddValidator(callerAddress, callerKey) }) } @@ -138,7 +139,7 @@ func TestPoS_AddValidator(t *testing.T) { } // Attempt to add the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.AddValidator(callerAddress, callerKey) }) @@ -240,7 +241,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(callerAddress) }) }) @@ -262,7 +263,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - testing.PanicsWithError(t, errValidatorNotOrigin, func() { + uassert.PanicsWithError(t, errValidatorNotOrigin, func() { p.RemoveValidator(initialSet[0].Address) }) }) @@ -291,7 +292,7 @@ func TestPoS_RemoveValidator(t *testing.T) { } // Attempt to add the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.AddValidator(callerAddress, callerKey) }) @@ -301,7 +302,7 @@ func TestPoS_RemoveValidator(t *testing.T) { } // Attempt to remove the validator - testing.NotPanics(t, func() { + uassert.NotPanics(t, func() { p.RemoveValidator(callerAddress) }) diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index a5ae7be05ad..6e55c5cc283 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -39,67 +39,6 @@ func Recover(result Setter) { panic(r) } -func extractPanicErr(r interface{}) string { - err, ok := r.(error) - if ok { - return err.Error() - } - - errStr, ok := r.(string) - if ok { - return errStr - } - - return "unknown error" -} - -// PanicsWithError asserts that the code inside the specified func panics, -// and that the recovered panic value is an error that satisfies the given message -func PanicsWithError(t *T, errString string, f func()) bool { - t.Helper() - - var valid bool - - defer func() { - if r := recover(); r != nil { - // Check if the error matches - panicErr := extractPanicErr(r) - if panicErr == errString { - valid = true - - return - } - - t.Fatalf("Function panicked with err, %s", panicErr) - } - }() - - // Run the callback - f() - - return valid -} - -// NotPanics asserts that the code inside the specified func does NOT panic -func NotPanics(t *T, f func()) bool { - t.Helper() - - valid := true - - defer func() { - if r := recover(); r != nil { - valid = false - - t.Fatalf("Function panicked with err, %s", extractPanicErr(r)) - } - }() - - // Run the callback - f() - - return valid -} - type Setter interface { Set(v interface{}) } From ce9329aa681a3b300cca27e9b2ee00f36e3dec56 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 19 Jun 2024 10:38:26 +0200 Subject: [PATCH 25/71] Add the 'testing' package to the whitelist --- gnovm/pkg/transpiler/transpiler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 8a91ae4a486..adb9de58e30 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -68,6 +68,7 @@ var stdlibWhitelist = []string{ "unicode", "unicode/utf8", "unicode/utf16", + "testing", // gno "std", From 2c85e06ecef26ee1ed92965852512bb9a2fa4564 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Thu, 20 Jun 2024 19:18:26 +0200 Subject: [PATCH 26/71] Revert "Move testing helpers to uassert" This reverts commit 2be75279b4f604a4d84463779cbe9250f106e544. --- examples/gno.land/p/demo/uassert/gno.mod | 1 - examples/gno.land/p/demo/uassert/uassert.gno | 64 ------------------- examples/gno.land/p/sys/vals/poa/gno.mod | 1 - examples/gno.land/p/sys/vals/poa/poa_test.gno | 17 +++-- examples/gno.land/p/sys/vals/poc/gno.mod | 1 - examples/gno.land/p/sys/vals/poc/poc_test.gno | 9 ++- examples/gno.land/p/sys/vals/pos/gno.mod | 1 - examples/gno.land/p/sys/vals/pos/pos_test.gno | 17 +++-- gnovm/stdlibs/testing/testing.gno | 61 ++++++++++++++++++ 9 files changed, 81 insertions(+), 91 deletions(-) delete mode 100644 examples/gno.land/p/demo/uassert/gno.mod delete mode 100644 examples/gno.land/p/demo/uassert/uassert.gno diff --git a/examples/gno.land/p/demo/uassert/gno.mod b/examples/gno.land/p/demo/uassert/gno.mod deleted file mode 100644 index a70e7db825d..00000000000 --- a/examples/gno.land/p/demo/uassert/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/uassert diff --git a/examples/gno.land/p/demo/uassert/uassert.gno b/examples/gno.land/p/demo/uassert/uassert.gno deleted file mode 100644 index dac4d3a5ed4..00000000000 --- a/examples/gno.land/p/demo/uassert/uassert.gno +++ /dev/null @@ -1,64 +0,0 @@ -package uassert - -import "testing" - -func extractPanicErr(r interface{}) string { - err, ok := r.(error) - if ok { - return err.Error() - } - - errStr, ok := r.(string) - if ok { - return errStr - } - - return "unknown error" -} - -// PanicsWithError asserts that the code inside the specified func panics, -// and that the recovered panic value is an error that satisfies the given message -func PanicsWithError(t *testing.T, errString string, f func()) bool { - t.Helper() - - var valid bool - - defer func() { - if r := recover(); r != nil { - // Check if the error matches - panicErr := extractPanicErr(r) - if panicErr == errString { - valid = true - - return - } - - t.Fatalf("Function panicked with err, %s", panicErr) - } - }() - - // Run the callback - f() - - return valid -} - -// NotPanics asserts that the code inside the specified func does NOT panic -func NotPanics(t *testing.T, f func()) bool { - t.Helper() - - valid := true - - defer func() { - if r := recover(); r != nil { - valid = false - - t.Fatalf("Function panicked with err, %s", extractPanicErr(r)) - } - }() - - // Run the callback - f() - - return valid -} diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod index a7439a81367..de760cae626 100644 --- a/examples/gno.land/p/sys/vals/poa/gno.mod +++ b/examples/gno.land/p/sys/vals/poa/gno.mod @@ -1,7 +1,6 @@ module gno.land/p/sys/vals/poa require ( - gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/poa/poa_test.gno b/examples/gno.land/p/sys/vals/poa/poa_test.gno index c6e5a7d64ea..bb0bbfd51b3 100644 --- a/examples/gno.land/p/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/p/sys/vals/poa/poa_test.gno @@ -4,7 +4,6 @@ import ( "std" "testing" - "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" ) @@ -49,7 +48,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - uassert.PanicsWithError(t, types.ErrValidatorExists, func() { + testing.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -69,7 +68,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -102,7 +101,7 @@ func TestPoA_AddValidator(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.AddValidator(proposedAddress, proposedKey) }) @@ -151,7 +150,7 @@ func TestPoA_AddValidator(t *testing.T) { std.TestSetOrigCaller(validator.Address) // Attempt to add the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.AddValidator(proposedAddress, proposedKey) }) } @@ -188,7 +187,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - uassert.PanicsWithError(t, errCallerNotValidator, func() { + testing.PanicsWithError(t, errCallerNotValidator, func() { p.RemoveValidator(callerAddress) }) }) @@ -210,7 +209,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(std.Address("totally random")) }) }) @@ -242,7 +241,7 @@ func TestPoA_RemoveValidator(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.RemoveValidator(proposedAddress) }) @@ -288,7 +287,7 @@ func TestPoA_RemoveValidator(t *testing.T) { std.TestSetOrigCaller(validator.Address) // Attempt to remove the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.RemoveValidator(proposedAddress) }) } diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod index ad8e2df0533..0263460b501 100644 --- a/examples/gno.land/p/sys/vals/poc/gno.mod +++ b/examples/gno.land/p/sys/vals/poc/gno.mod @@ -1,7 +1,6 @@ module gno.land/p/sys/vals/poc require ( - gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno index 96ee2aae14d..a6b63e97dc0 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -5,7 +5,6 @@ import ( "std" - "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" ) @@ -47,7 +46,7 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { p := NewPoC(WithInitialSet(initialSet)) // Attempt to add the validator - uassert.PanicsWithError(t, types.ErrValidatorExists, func() { + testing.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(proposalAddress, proposalKey) }) }) @@ -65,7 +64,7 @@ func TestPoC_AddValidator(t *testing.T) { p := NewPoC() // Attempt to add the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.AddValidator(proposalAddress, proposalKey) }) @@ -92,7 +91,7 @@ func TestPoC_RemoveValidator_Invalid(t *testing.T) { p := NewPoC(WithInitialSet(initialSet)) // Attempt to remove the validator - uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(std.Address("totally random")) }) }) @@ -112,7 +111,7 @@ func TestPoC_RemoveValidator(t *testing.T) { p := NewPoC(WithInitialSet(initialSet)) // Attempt to remove the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.RemoveValidator(proposalAddress) }) diff --git a/examples/gno.land/p/sys/vals/pos/gno.mod b/examples/gno.land/p/sys/vals/pos/gno.mod index bab923d0d4f..585c7b6afc8 100644 --- a/examples/gno.land/p/sys/vals/pos/gno.mod +++ b/examples/gno.land/p/sys/vals/pos/gno.mod @@ -1,7 +1,6 @@ module gno.land/p/sys/vals/pos require ( - gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/pos/pos_test.gno b/examples/gno.land/p/sys/vals/pos/pos_test.gno index 4f5349a658b..bdb6e558c31 100644 --- a/examples/gno.land/p/sys/vals/pos/pos_test.gno +++ b/examples/gno.land/p/sys/vals/pos/pos_test.gno @@ -4,7 +4,6 @@ import ( "std" "testing" - "gno.land/p/demo/uassert" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" ) @@ -49,7 +48,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to add the validator - uassert.PanicsWithError(t, types.ErrValidatorExists, func() { + testing.PanicsWithError(t, types.ErrValidatorExists, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -71,7 +70,7 @@ func TestPoS_AddValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(std.Address("random address")) // Attempt to add the validator - uassert.PanicsWithError(t, errValidatorNotOrigin, func() { + testing.PanicsWithError(t, errValidatorNotOrigin, func() { p.AddValidator(callerAddress, callerKey) }) }) @@ -105,7 +104,7 @@ func TestPoS_AddValidator(t *testing.T) { } // Attempt to add the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.AddValidator(callerAddress, callerKey) }) } @@ -139,7 +138,7 @@ func TestPoS_AddValidator(t *testing.T) { } // Attempt to add the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.AddValidator(callerAddress, callerKey) }) @@ -241,7 +240,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - uassert.PanicsWithError(t, types.ErrValidatorMissing, func() { + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { p.RemoveValidator(callerAddress) }) }) @@ -263,7 +262,7 @@ func TestPoS_RemoveValidator_Invalid(t *testing.T) { std.TestSetOrigCaller(callerAddress) // Attempt to remove the validator - uassert.PanicsWithError(t, errValidatorNotOrigin, func() { + testing.PanicsWithError(t, errValidatorNotOrigin, func() { p.RemoveValidator(initialSet[0].Address) }) }) @@ -292,7 +291,7 @@ func TestPoS_RemoveValidator(t *testing.T) { } // Attempt to add the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.AddValidator(callerAddress, callerKey) }) @@ -302,7 +301,7 @@ func TestPoS_RemoveValidator(t *testing.T) { } // Attempt to remove the validator - uassert.NotPanics(t, func() { + testing.NotPanics(t, func() { p.RemoveValidator(callerAddress) }) diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index 6e55c5cc283..a5ae7be05ad 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -39,6 +39,67 @@ func Recover(result Setter) { panic(r) } +func extractPanicErr(r interface{}) string { + err, ok := r.(error) + if ok { + return err.Error() + } + + errStr, ok := r.(string) + if ok { + return errStr + } + + return "unknown error" +} + +// PanicsWithError asserts that the code inside the specified func panics, +// and that the recovered panic value is an error that satisfies the given message +func PanicsWithError(t *T, errString string, f func()) bool { + t.Helper() + + var valid bool + + defer func() { + if r := recover(); r != nil { + // Check if the error matches + panicErr := extractPanicErr(r) + if panicErr == errString { + valid = true + + return + } + + t.Fatalf("Function panicked with err, %s", panicErr) + } + }() + + // Run the callback + f() + + return valid +} + +// NotPanics asserts that the code inside the specified func does NOT panic +func NotPanics(t *T, f func()) bool { + t.Helper() + + valid := true + + defer func() { + if r := recover(); r != nil { + valid = false + + t.Fatalf("Function panicked with err, %s", extractPanicErr(r)) + } + }() + + // Run the callback + f() + + return valid +} + type Setter interface { Set(v interface{}) } From 40cf5bb36a69032ae54de3e2a066ccffbd49d44e Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Thu, 20 Jun 2024 19:22:06 +0200 Subject: [PATCH 27/71] Tidy go mods --- contribs/gnodev/go.mod | 1 + contribs/gnodev/go.sum | 56 ++++++++++++++-------------------------- contribs/gnokeykc/go.mod | 1 + contribs/gnokeykc/go.sum | 52 +++++++++++++------------------------ 4 files changed, 40 insertions(+), 70 deletions(-) diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index cbae5be93ea..b7a757c36a5 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -56,6 +56,7 @@ require ( go.etcd.io/bbolt v1.3.9 // indirect go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect diff --git a/contribs/gnodev/go.sum b/contribs/gnodev/go.sum index e15a137fabe..81d7a5a103e 100644 --- a/contribs/gnodev/go.sum +++ b/contribs/gnodev/go.sum @@ -95,8 +95,7 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gotuna/gotuna v0.6.0 h1:N1lQKXEi/lwRp8u3sccTYLhzOffA4QasExz/1M5Riws= github.com/gotuna/gotuna v0.6.0/go.mod h1:F/ecRt29ChB6Ycy1AFIBpBiMNK0j7Heq+gFbLWquhjc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -165,25 +164,21 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -196,8 +191,7 @@ go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= @@ -208,12 +202,11 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -224,17 +217,14 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= @@ -242,16 +232,11 @@ golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -259,8 +244,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 7d29d04e492..4a45cbc83f2 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -38,6 +38,7 @@ require ( github.com/zondax/ledger-go v0.14.3 // indirect go.opentelemetry.io/otel v1.27.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect diff --git a/contribs/gnokeykc/go.sum b/contribs/gnokeykc/go.sum index 8d9fa5774d4..2e72631bd10 100644 --- a/contribs/gnokeykc/go.sum +++ b/contribs/gnokeykc/go.sum @@ -80,8 +80,7 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -135,31 +134,26 @@ github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfU github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 h1:hDKnobznDpcdTlNzO0S/owRB8tyVr1OoeZZhDoqY+Cs= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0/go.mod h1:kUDQaUs1h8iTIHbQTk+iJRiUvSfJYMMKTtMCaiVu7B0= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/metric v1.25.0 h1:7CiHOy08LbrxMAp4vWpbiPcklunUshVpAvGBrdDRlGw= -go.opentelemetry.io/otel/sdk/metric v1.25.0/go.mod h1:LzwoKptdbBBdYfvtGCzGwk6GWMA3aUzBOwtQpR6Nz7o= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= @@ -170,8 +164,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -183,32 +176,24 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8= -google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -216,8 +201,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From a28623f9a5eb693f04066bbd412ba67dea81a02f Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 09:50:04 +0200 Subject: [PATCH 28/71] Drop PoA / PoS in this scope --- examples/gno.land/p/sys/vals/poa/gno.mod | 6 - examples/gno.land/p/sys/vals/poa/option.gno | 19 -- examples/gno.land/p/sys/vals/poa/poa.gno | 206 ------------ examples/gno.land/p/sys/vals/poa/poa_test.gno | 310 ----------------- examples/gno.land/p/sys/vals/poa/vote.gno | 99 ------ examples/gno.land/p/sys/vals/pos/gno.mod | 6 - examples/gno.land/p/sys/vals/pos/option.gno | 22 -- examples/gno.land/p/sys/vals/pos/pos.gno | 213 ------------ examples/gno.land/p/sys/vals/pos/pos_test.gno | 312 ------------------ 9 files changed, 1193 deletions(-) delete mode 100644 examples/gno.land/p/sys/vals/poa/gno.mod delete mode 100644 examples/gno.land/p/sys/vals/poa/option.gno delete mode 100644 examples/gno.land/p/sys/vals/poa/poa.gno delete mode 100644 examples/gno.land/p/sys/vals/poa/poa_test.gno delete mode 100644 examples/gno.land/p/sys/vals/poa/vote.gno delete mode 100644 examples/gno.land/p/sys/vals/pos/gno.mod delete mode 100644 examples/gno.land/p/sys/vals/pos/option.gno delete mode 100644 examples/gno.land/p/sys/vals/pos/pos.gno delete mode 100644 examples/gno.land/p/sys/vals/pos/pos_test.gno diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod deleted file mode 100644 index de760cae626..00000000000 --- a/examples/gno.land/p/sys/vals/poa/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/sys/vals/poa - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/p/sys/vals/poa/option.gno b/examples/gno.land/p/sys/vals/poa/option.gno deleted file mode 100644 index d551525daaf..00000000000 --- a/examples/gno.land/p/sys/vals/poa/option.gno +++ /dev/null @@ -1,19 +0,0 @@ -package poa - -import "gno.land/p/sys/vals/types" - -type Option func(*PoA) - -// WithInitialSet sets the initial PoA validator set -func WithInitialSet(validators []*types.Validator) Option { - return func(p *PoA) { - for index, validator := range validators { - p.validators = append(p.validators, validator) - p.addressToValidatorIndex[validator.Address] = index - - p.totalVotingPower += validator.VotingPower - } - - p.majorityPower = (2 * p.totalVotingPower) / 3 - } -} diff --git a/examples/gno.land/p/sys/vals/poa/poa.gno b/examples/gno.land/p/sys/vals/poa/poa.gno deleted file mode 100644 index 45162a728ed..00000000000 --- a/examples/gno.land/p/sys/vals/poa/poa.gno +++ /dev/null @@ -1,206 +0,0 @@ -package poa - -import ( - "std" - - "gno.land/p/sys/vals/types" -) - -const ( - UserVotedAddEvent = "UserVotedAdd" // emitted when someone votes to add a validator - UserVotedRemoveEvent = "UserVotedRemove" // emitted when someone votes to remove a validator -) - -var errCallerNotValidator = "caller is not validator" - -// PoA specifies the Proof of Authority validator set. -// In order to become part of the set, users to be voted into -// the validator set by the majority of the existing set -type PoA struct { - // validators holds the current validator set. - // This slice can never practically grow more than ~150 elements, - // due to Tendermint's quadratic network complexity - validators []*types.Validator - addressToValidatorIndex map[std.Address]int // address -> index - votes *votingSystem // system that keeps track of votes - - totalVotingPower uint64 // cached value for quick lookups - majorityPower uint64 // cached value for quick lookups -} - -// NewPoA creates a new empty Proof of Authority validator set -func NewPoA(opts ...Option) *PoA { - // Create the empty set - p := &PoA{ - validators: make([]*types.Validator, 0), - addressToValidatorIndex: make(map[std.Address]int), - votes: newVotingSystem(), - } - - // Apply the options - for _, opt := range opts { - opt(p) - } - - return p -} - -// AddValidator adds a vote to add a new validator to the validator set. -// -// Criteria for being added: -// - caller is a user account -// - caller is a validator -// - the proposed address is not a validator -// - a 2/3+ majority of the set have voted to add -func (p *PoA) AddValidator(address std.Address, pubKey string) *types.Validator { - caller := std.GetOrigCaller() - - // Validate that the operation is a valid call - p.validateAdd(caller, address) - - // Cast the vote - voterPower := p.validators[p.addressToValidatorIndex[caller]].VotingPower - p.votes.castVote(caller, address, voterPower, true) - - // Emit the vote event - std.Emit(UserVotedAddEvent) - - // Calculate the votes - addCount, _ := p.votes.getTally(address) - - // Check if there is a majority - // to apply the action - if addCount <= p.majorityPower { - // No super majority to add - return nil - } - - // Execute the decision - return p.addValidator(address, pubKey) -} - -// validateAdd validates a validator add call -func (p *PoA) validateAdd(caller, address std.Address) { - // Check if the caller is a user account - if !std.PrevRealm().IsUser() { - panic(types.ErrCallerNotUserAccount) - } - - // Check if the caller is in the set - if !p.IsValidator(caller) { - panic(types.ErrValidatorMissing) - } - - // Check if the validator is already in the set - if p.IsValidator(address) { - panic(types.ErrValidatorExists) - } -} - -// addValidator adds the given validator to the PoA validator set -func (p *PoA) addValidator(address std.Address, pubKey string) *types.Validator { - v := &types.Validator{ - Address: address, - PubKey: pubKey, // TODO: in the future, verify the public key - VotingPower: 1, // in this PoA system, all new validators have the same voting power - } - - // Add the validator to the set - p.addressToValidatorIndex[v.Address] = len(p.validators) - p.validators = append(p.validators, v) - - // Update the total voting power - p.totalVotingPower += v.VotingPower - p.majorityPower = (2 * p.totalVotingPower) / 3 - - // Remove the candidate from the voting system - p.votes.resetCandidate(v.Address) - - return v -} - -// RemoveValidator adds a vote to remove a validator from the validator set. -// -// Criteria for being added: -// - caller is a user account -// - caller is a validator -// - the proposed address is a validator -// - a 2/3+ majority of the set have voted to remove -func (p *PoA) RemoveValidator(address std.Address) *types.Validator { - caller := std.GetOrigCaller() - - // Validate that the operation is a valid call - p.validateRemove(caller, address) - - // Cast the vote - voterPower := p.validators[p.addressToValidatorIndex[caller]].VotingPower - p.votes.castVote(caller, address, voterPower, false) - - // Emit the vote event - std.Emit(UserVotedRemoveEvent) - - // Calculate the votes - _, removeCount := p.votes.getTally(address) - - // Check if there is a majority - // to apply the action - if removeCount <= p.majorityPower { - // No super majority to remove - return nil - } - - // Execute the decision - return p.removeValidator(address) -} - -// validateRemove validates a validator remove call -func (p *PoA) validateRemove(caller, address std.Address) { - // Check if the caller is a user account - if !std.PrevRealm().IsUser() { - panic(types.ErrCallerNotUserAccount) - } - - // Check if this request came from a validator - if !p.IsValidator(caller) { - panic(errCallerNotValidator) - } - - // Check if the address is a validator - if !p.IsValidator(address) { - panic(types.ErrValidatorMissing) - } -} - -// removeValidator removes the given address from the PoA validator set -func (p *PoA) removeValidator(address std.Address) *types.Validator { - // Fetch the validator index - index := p.addressToValidatorIndex[address] - - // Remove the validator from the set - validator := p.validators[index] - p.validators = append(p.validators[:index], p.validators[index+1:]...) - - delete(p.addressToValidatorIndex, address) - - // Update the total voting power - p.totalVotingPower -= validator.VotingPower - p.majorityPower = (2 * p.totalVotingPower) / 3 - - // Remove the candidate from the voting system - p.votes.resetCandidate(address) - - return validator -} - -// IsValidator returns a flag indicating if the address -// is part of the staked validator set -func (p *PoA) IsValidator(address std.Address) bool { - _, exists := p.addressToValidatorIndex[address] - - return exists -} - -// GetValidators returns the current staked validator set -func (p *PoA) GetValidators() []*types.Validator { - return p.validators -} diff --git a/examples/gno.land/p/sys/vals/poa/poa_test.gno b/examples/gno.land/p/sys/vals/poa/poa_test.gno deleted file mode 100644 index bb0bbfd51b3..00000000000 --- a/examples/gno.land/p/sys/vals/poa/poa_test.gno +++ /dev/null @@ -1,310 +0,0 @@ -package poa - -import ( - "std" - "testing" - - "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/types" -) - -// generateTestValidators generates a dummy validator set -func generateTestValidators(count int) []*types.Validator { - vals := make([]*types.Validator, 0, count) - - for i := 0; i < count; i++ { - val := &types.Validator{ - Address: std.Address(ufmt.Sprintf("%d", i)), - PubKey: "public-key", - VotingPower: 1, - } - - vals = append(vals, val) - } - - return vals -} - -func TestPoA_AddValidator_Invalid(t *testing.T) { - t.Parallel() - - t.Run("validator already in set", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(1) - ) - - initialSet[0].Address = callerAddress - initialSet[0].PubKey = callerKey - - // Create the protocol with an initial set - p := NewPoA(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorExists, func() { - p.AddValidator(callerAddress, callerKey) - }) - }) - - t.Run("caller not validator", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - ) - - // Create the protocol with no initial set - p := NewPoA() - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { - p.AddValidator(callerAddress, callerKey) - }) - }) -} - -func TestPoA_AddValidator(t *testing.T) { - t.Parallel() - - t.Run("validator voted in by single node", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - proposedAddress = std.Address("proposal") - proposedKey = "proposed-key" - - initialSet = generateTestValidators(1) - ) - - initialSet[0].Address = callerAddress - initialSet[0].PubKey = callerKey - initialSet[0].VotingPower = 1 - - // Create the protocol with an initial set - p := NewPoA(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to add the validator - testing.NotPanics(t, func() { - p.AddValidator(proposedAddress, proposedKey) - }) - - // Make sure the validator is added - if !p.IsValidator(proposedAddress) { - t.Fatal("address is not validator") - } - - // Make sure the total voting power is changed - if p.totalVotingPower != 2 { - t.Fatal("invalid total voting power") - } - - // Make sure the majority voting power is changed - if p.majorityPower != 1 { // 50% is the 2/3 majority for 2 nodes - t.Fatal("invalid majority voting power") - } - }) - - t.Run("validator voted in by majority", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - proposedAddress = std.Address("proposal") - proposedKey = "proposed-key" - - initialSet = generateTestValidators(3) - ) - - // Prepare the initial validator set - for index, validator := range initialSet { - validator.Address = std.Address(ufmt.Sprintf("caller-%d", index)) - validator.PubKey = ufmt.Sprintf("caller-key-%d", index) - validator.VotingPower = 1 - } - - // Create the protocol with an initial set - p := NewPoA(WithInitialSet(initialSet)) - - // Add in the votes - for _, validator := range initialSet { - // Set the origin caller - std.TestSetOrigCaller(validator.Address) - - // Attempt to add the validator - testing.NotPanics(t, func() { - p.AddValidator(proposedAddress, proposedKey) - }) - } - - // Make sure the validator is added - if !p.IsValidator(proposedAddress) { - t.Fatal("address is not validator") - } - - // Make sure the total voting power is changed - if p.totalVotingPower != 4 { - t.Fatal("invalid total voting power") - } - - // Make sure the majority voting power is changed - if p.majorityPower != 2 { // 2 * 4 / 3 - t.Fatal("invalid majority voting power") - } - }) -} - -func TestPoA_RemoveValidator_Invalid(t *testing.T) { - t.Parallel() - - t.Run("validator not in set", func(t *testing.T) { - t.Parallel() - - callerAddress := std.Address("caller") - - // Create the protocol with no initial set - p := NewPoA() - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to remove the validator - testing.PanicsWithError(t, errCallerNotValidator, func() { - p.RemoveValidator(callerAddress) - }) - }) - - t.Run("proposed removal not in set", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - initialSet = generateTestValidators(1) - ) - - initialSet[0].Address = callerAddress - - // Create the protocol with an initial set - p := NewPoA(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { - p.RemoveValidator(std.Address("totally random")) - }) - }) -} - -func TestPoA_RemoveValidator(t *testing.T) { - t.Parallel() - - t.Run("validator voted out by single node", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(2) - ) - - initialSet[0].Address = callerAddress - initialSet[0].PubKey = callerKey - initialSet[0].VotingPower = 10 - - proposedAddress := initialSet[1].Address - - // Create the protocol with an initial set - p := NewPoA(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to remove the validator - testing.NotPanics(t, func() { - p.RemoveValidator(proposedAddress) - }) - - // Make sure the validator is removed - if p.IsValidator(proposedAddress) { - t.Fatal("address is still a validator") - } - - // Make sure the total voting power is changed - if p.totalVotingPower != 10 { - t.Fatal("invalid total voting power") - } - - // Make sure the majority voting power is changed - if p.majorityPower != 6 { - t.Fatal("invalid majority voting power") - } - }) - - t.Run("validator voted in by majority", func(t *testing.T) { - t.Parallel() - - initialSet := generateTestValidators(4) - - // Prepare the initial validator set - for index, validator := range initialSet { - validator.Address = std.Address(ufmt.Sprintf("caller-%d", index)) - validator.PubKey = ufmt.Sprintf("caller-key-%d", index) - validator.VotingPower = 1 - } - - var ( - proposedAddress = initialSet[len(initialSet)-1].Address // last validator - voters = initialSet[:len(initialSet)-1] - ) - - // Create the protocol with an initial set - p := NewPoA(WithInitialSet(initialSet)) - - // Add in the votes - for _, validator := range voters { - // Set the origin caller - std.TestSetOrigCaller(validator.Address) - - // Attempt to remove the validator - testing.NotPanics(t, func() { - p.RemoveValidator(proposedAddress) - }) - } - - // Make sure the validator is removed - if p.IsValidator(proposedAddress) { - t.Fatal("address is validator") - } - - // Make sure the total voting power is changed - if p.totalVotingPower != 3 { - t.Fatal("invalid total voting power") - } - - // Make sure the majority voting power is changed - if p.majorityPower != 2 { // 2 * 3 / 3 - t.Fatal("invalid majority voting power") - } - }) -} diff --git a/examples/gno.land/p/sys/vals/poa/vote.gno b/examples/gno.land/p/sys/vals/poa/vote.gno deleted file mode 100644 index 566c556de2b..00000000000 --- a/examples/gno.land/p/sys/vals/poa/vote.gno +++ /dev/null @@ -1,99 +0,0 @@ -package poa - -import ( - "std" - "strings" - - "gno.land/p/demo/ufmt" -) - -type ( - tallyMap map[std.Address]tally // address -> current tally - haveVotedMap map[string]struct{} // quick lookup map -) - -// tally keeps track of active for / against votes -type tally struct { - toAdd uint64 - toRemove uint64 -} - -// votingSystem wraps the validator voting -type votingSystem struct { - tallyMap tallyMap - haveVotedMap haveVotedMap -} - -// newVotingSystem creates a new PoA voting system -func newVotingSystem() *votingSystem { - return &votingSystem{ - tallyMap: make(tallyMap), - haveVotedMap: make(haveVotedMap), - } -} - -// castVote casts a new vote to the given candidate -func (v *votingSystem) castVote( - voter, - candidate std.Address, - voterPower uint64, - toAdd bool, -) { - // Construct a unique proposal key (voterAddr_candidateAddr) - votedKey := ufmt.Sprintf( - "%s_%s", - voter.String(), - candidate.String(), - ) - - if _, voted := v.haveVotedMap[votedKey]; voted { - // Vote already cast, noop - return - } - - // Fetch the tally - t, exists := v.tallyMap[candidate] - if !exists { - t = tally{ - toAdd: 0, - toRemove: 0, - } - } - - // Update the tally - if toAdd { - t.toAdd += voterPower - } else { - t.toRemove += voterPower - } - - // Save the tally - v.tallyMap[candidate] = t - v.haveVotedMap[votedKey] = struct{}{} -} - -// getTally returns the current voting tally for a candidate -func (v *votingSystem) getTally(candidate std.Address) (uint64, uint64) { - t, exists := v.tallyMap[candidate] - if !exists { - return 0, 0 - } - - return t.toAdd, t.toRemove -} - -// resetCandidate removes a candidate from the voting system -func (v *votingSystem) resetCandidate(candidate std.Address) { - // Drop the tally entries - delete(v.tallyMap, candidate) - - // Drop the tracked votes - candidateStr := candidate.String() - for key := range v.haveVotedMap { - if !strings.Contains(key, candidateStr) { - continue - } - - delete(v.haveVotedMap, key) - } -} diff --git a/examples/gno.land/p/sys/vals/pos/gno.mod b/examples/gno.land/p/sys/vals/pos/gno.mod deleted file mode 100644 index 585c7b6afc8..00000000000 --- a/examples/gno.land/p/sys/vals/pos/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/p/sys/vals/pos - -require ( - gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest -) diff --git a/examples/gno.land/p/sys/vals/pos/option.gno b/examples/gno.land/p/sys/vals/pos/option.gno deleted file mode 100644 index 6844734dfde..00000000000 --- a/examples/gno.land/p/sys/vals/pos/option.gno +++ /dev/null @@ -1,22 +0,0 @@ -package pos - -import ( - "std" - - "gno.land/p/sys/vals/types" -) - -var initialStake = std.Coin{"ugnot", 1000} - -type Option func(*PoS) - -// WithInitialSet sets the initial PoS validator set, with empty stakes -func WithInitialSet(validators []*types.Validator) Option { - return func(p *PoS) { - for index, validator := range validators { - p.validators = append(p.validators, validator) - p.addressToValidatorIndex[validator.Address] = index - p.addressToStakedAmount[validator.Address] = initialStake - } - } -} diff --git a/examples/gno.land/p/sys/vals/pos/pos.gno b/examples/gno.land/p/sys/vals/pos/pos.gno deleted file mode 100644 index dcdd0e2b594..00000000000 --- a/examples/gno.land/p/sys/vals/pos/pos.gno +++ /dev/null @@ -1,213 +0,0 @@ -package pos - -import ( - "std" - - "gno.land/p/sys/vals/types" -) - -var errValidatorNotOrigin = "validator address is not origin" - -var stakeThreshold = std.Coin{"ugnot", int64(100000000)} // 100 GNOTs - -const ( - UserStakedEvent = "UserStaked" // emitted when a user stakes funds - UserUnstakedEvent = "UserUnstaked" // emitted when a user unstakes funds -) - -// PoS specifies the Proof of Stake validator set. -// In order to become part of the set, users need to stake funds (at least at the threshold). -// Upon leaving the set, users receive their staked funds back -type PoS struct { - // validators holds the current validator set. - // This slice can never practically grow more than ~150 elements, - // due to Tendermint's quadratic network complexity - validators []*types.Validator - addressToValidatorIndex map[std.Address]int // address -> index - addressToStakedAmount map[std.Address]std.Coin // address -> staked amount - - totalStake uint64 // the total staked amount -} - -// NewPoS creates a new empty Proof of Stake validator set -func NewPoS(opts ...Option) *PoS { - // Create the empty set - p := &PoS{ - validators: make([]*types.Validator, 0), - addressToValidatorIndex: make(map[std.Address]int), - addressToStakedAmount: make(map[std.Address]std.Coin), - } - - // Apply the options - for _, opt := range opts { - opt(p) - } - - return p -} - -// AddValidator adds a new validator to the validator set. -// -// Criteria for being added: -// - caller is a user account -// - caller is not already a validator -// - caller has staked funds >= the current staking threshold -func (p *PoS) AddValidator(address std.Address, pubKey string) *types.Validator { - caller := std.GetOrigCaller() - - // Validate the addition - p.validateAdd(caller, address) - - // Extract the sent amount with the call - stakedAmount := std.GetOrigSend().AmountOf("ugnot") - - // Fetch the already staked amount - addressStake, exists := p.addressToStakedAmount[caller] - if !exists { - addressStake = std.Coin{"ugnot", int64(0)} - } - - // Save the staked amount - addressStake.Amount += stakedAmount - p.addressToStakedAmount[caller] = addressStake - p.totalStake += uint64(stakedAmount) - - // Emit the event that the user staked funds - std.Emit(UserStakedEvent) - - // Check if the caller can become a validator - if !p.canBecomeValidator(caller) { - return nil - } - - // Add the caller to the validator set - v := &types.Validator{ - Address: caller, - PubKey: pubKey, - VotingPower: uint64(addressStake.Amount) / p.totalStake, // voting power is proportional to the stake - } - - p.addressToValidatorIndex[caller] = len(p.validators) - p.validators = append(p.validators, v) - - // Rebalance the voting power - p.rebalanceVotingPower() - - // Emit the validator set change - std.Emit(types.ValidatorAddedEvent) - - return v -} - -// validateAdd validates a validator add call -func (p *PoS) validateAdd(caller, address std.Address) { - // Check if the caller is a user account - if !std.PrevRealm().IsUser() { - panic(types.ErrCallerNotUserAccount) - } - - // Check if the validator is already in the set - if p.IsValidator(address) { - panic(types.ErrValidatorExists) - } - - // Check if the caller is supplying their own address - if address != caller { - panic(errValidatorNotOrigin) - } -} - -// rebalanceVotingPower rebalances the voting power of the validator set -func (p *PoS) rebalanceVotingPower() { - for _, v := range p.validators { - stakedAmount := p.addressToStakedAmount[v.Address] - v.VotingPower = uint64(stakedAmount.Amount) / p.totalStake - } -} - -// canBecomeValidator checks if the address fulfills all criteria for -// becoming a validator: -// - is not already a validator -// - has staked >= the staking threshold -func (p *PoS) canBecomeValidator(address std.Address) bool { - stake, _ := p.addressToStakedAmount[address] - - return !p.IsValidator(address) && stake.IsGTE(stakeThreshold) -} - -// RemoveValidator removes a validator from the validator set. -// Upon successful removal, the staked funds are returned to the user -// -// Criteria for being removed: -// - caller is a user account -// - caller is a validator -func (p *PoS) RemoveValidator(address std.Address) *types.Validator { - // Check if the caller is a user account - if !std.PrevRealm().IsUser() { - panic(types.ErrCallerNotUserAccount) - } - - // Check if this request came from a validator - if !p.IsValidator(address) { - panic(types.ErrValidatorMissing) - } - - var ( - caller = std.GetOrigCaller() - addressStake = p.addressToStakedAmount[caller] - ) - - // Check if the caller is supplying their own address - if address != caller { - panic(errValidatorNotOrigin) - } - - // Get the validator index - index := p.addressToValidatorIndex[caller] - validator := p.validators[index] - - // Remove the validator from the set - p.validators = append(p.validators[:index], p.validators[index+1:]...) - - delete(p.addressToValidatorIndex, caller) - delete(p.addressToStakedAmount, caller) - - // Return the stake - returnStake(caller, addressStake) - - // Emit the validator set change - std.Emit(types.ValidatorRemovedEvent) - - // Emit the unstake event - std.Emit(UserUnstakedEvent) - - // Rebalance the voting power - p.rebalanceVotingPower() - - return validator -} - -// returnStake returns the specified stake to the given address -func returnStake(address std.Address, amount std.Coin) { - // Get the current package address - from := std.CurrentRealm().Addr() - - // Fetch the banker - banker := std.GetBanker(std.BankerTypeRealmSend) - - // Return the staked funds - banker.SendCoins(from, address, std.Coins{amount}) -} - -// IsValidator returns a flag indicating if the address -// is part of the staked validator set -func (p *PoS) IsValidator(address std.Address) bool { - _, exists := p.addressToValidatorIndex[std.Address(address)] - - return exists -} - -// GetValidators returns the current staked validator set -func (p *PoS) GetValidators() []*types.Validator { - return p.validators -} diff --git a/examples/gno.land/p/sys/vals/pos/pos_test.gno b/examples/gno.land/p/sys/vals/pos/pos_test.gno deleted file mode 100644 index bdb6e558c31..00000000000 --- a/examples/gno.land/p/sys/vals/pos/pos_test.gno +++ /dev/null @@ -1,312 +0,0 @@ -package pos - -import ( - "std" - "testing" - - "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/types" -) - -// generateTestValidators generates a dummy validator set -func generateTestValidators(count int) []*types.Validator { - vals := make([]*types.Validator, 0, count) - - for i := 0; i < count; i++ { - val := &types.Validator{ - Address: std.Address(ufmt.Sprintf("%d", i)), - PubKey: "public-key", - VotingPower: 0, - } - - vals = append(vals, val) - } - - return vals -} - -func TestPoS_AddValidator_Invalid(t *testing.T) { - t.Parallel() - - t.Run("validator already in set", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(1) - ) - - initialSet[0].Address = callerAddress - initialSet[0].PubKey = callerKey - - // Create the protocol with no initial set - p := NewPoS(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorExists, func() { - p.AddValidator(callerAddress, callerKey) - }) - }) - - t.Run("validator address is not the origin", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(10) - ) - - // Create the protocol with an initial set - p := NewPoS(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(std.Address("random address")) - - // Attempt to add the validator - testing.PanicsWithError(t, errValidatorNotOrigin, func() { - p.AddValidator(callerAddress, callerKey) - }) - }) -} - -func TestPoS_AddValidator(t *testing.T) { - t.Parallel() - - t.Run("user becomes validator in a few tries", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - numTries = int64(10) - - value = std.Coins{{"ugnot", stakeThreshold.Amount / numTries}} - ) - - // Create the protocol with no initial set - p := NewPoS() - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - std.TestSetOrigSend(value, std.Coins{}) - - for i := int64(0); i < numTries; i++ { - if p.IsValidator(callerAddress) { - t.Fatalf("should not have a validator at try %d", i) - } - - // Attempt to add the validator - testing.NotPanics(t, func() { - p.AddValidator(callerAddress, callerKey) - }) - } - - // Make sure the user became a validator - if !p.IsValidator(callerAddress) { - t.Fatalf("should be a validator") - } - }) - - t.Run("user becomes validator in single stake (threshold)", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - value = std.Coins{{"ugnot", stakeThreshold.Amount}} - ) - - // Create the protocol with an initial set - p := NewPoS() - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - std.TestSetOrigSend(value, std.Coins{}) - - // Make sure the caller is not a validator - if p.IsValidator(callerAddress) { - t.Fatalf("should not be a validator") - } - - // Attempt to add the validator - testing.NotPanics(t, func() { - p.AddValidator(callerAddress, callerKey) - }) - - // Make sure the user became a validator - if !p.IsValidator(callerAddress) { - t.Fatalf("should be a validator") - } - }) -} - -func TestPoS_IsValidator(t *testing.T) { - t.Parallel() - - t.Run("address is not in the set", func(t *testing.T) { - t.Parallel() - - // Empty PoS set - p := NewPoS() - - if p.IsValidator(std.Address("random address")) { - t.Fatal("should not be a validator") - } - }) - - t.Run("address is in the set", func(t *testing.T) { - t.Parallel() - - initialSet := generateTestValidators(10) - - // Existing PoS set - p := NewPoS(WithInitialSet(initialSet)) - - if !p.IsValidator(initialSet[0].Address) { - t.Fatal("should be a validator") - } - }) -} - -func TestPoS_GetValidators(t *testing.T) { - t.Parallel() - - t.Run("empty set", func(t *testing.T) { - t.Parallel() - - // Empty PoS set - p := NewPoS() - - if len(p.GetValidators()) != 0 { - t.Fatal("should be empty set") - } - }) - - t.Run("existing set", func(t *testing.T) { - t.Parallel() - - initialSet := generateTestValidators(10) - - // Existing PoS set - p := NewPoS(WithInitialSet(initialSet)) - - vals := p.GetValidators() - - // Make sure the set has a valid length - if len(vals) != len(initialSet) { - t.Fatal("invalid set length") - } - - // Make sure each validator is valid - for i, val := range vals { - ref := initialSet[i] - - if val.Address != ref.Address { - t.Fatal("address mismatch") - } - - if val.PubKey != ref.PubKey { - t.Fatal("address mismatch") - } - - if val.VotingPower != 0 { - t.Fatal("invalid voting power") - } - } - }) -} - -func TestPoS_RemoveValidator_Invalid(t *testing.T) { - t.Parallel() - - t.Run("validator not in set", func(t *testing.T) { - t.Parallel() - - callerAddress := std.Address("caller") - - // Create the protocol with no initial set - p := NewPoS() - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { - p.RemoveValidator(callerAddress) - }) - }) - - t.Run("validator address is not the origin", func(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("caller") - callerKey = "public-key" - - initialSet = generateTestValidators(10) - ) - - // Create the protocol with an initial set - p := NewPoS(WithInitialSet(initialSet)) - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - - // Attempt to remove the validator - testing.PanicsWithError(t, errValidatorNotOrigin, func() { - p.RemoveValidator(initialSet[0].Address) - }) - }) -} - -func TestPoS_RemoveValidator(t *testing.T) { - t.Parallel() - - var ( - callerAddress = std.Address("g1d4y807day8r5z54a54rkjheq3l50l5e0xmceem") - callerKey = "public-key" - - value = std.Coins{{"ugnot", stakeThreshold.Amount}} - ) - - // Create an initial PoS protocol with no set - p := NewPoS() - - // Set the origin caller - std.TestSetOrigCaller(callerAddress) - std.TestSetOrigSend(value, std.Coins{}) - - // Make sure the caller is not a validator - if p.IsValidator(callerAddress) { - t.Fatalf("should not be a validator") - } - - // Attempt to add the validator - testing.NotPanics(t, func() { - p.AddValidator(callerAddress, callerKey) - }) - - // Make sure the user became a validator - if !p.IsValidator(callerAddress) { - t.Fatalf("should be a validator") - } - - // Attempt to remove the validator - testing.NotPanics(t, func() { - p.RemoveValidator(callerAddress) - }) - - // Make sure the validator is removed - if p.IsValidator(callerAddress) { - t.Fatalf("should not be a validator") - } -} From 2d365dda3bfe76905ce3007a72f88ec6cfe708cc Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 09:55:55 +0200 Subject: [PATCH 29/71] Temporary rename --- examples/gno.land/r/sys/vals/{vals.gno => validators.gno} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/r/sys/vals/{vals.gno => validators.gno} (100%) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/validators.gno similarity index 100% rename from examples/gno.land/r/sys/vals/vals.gno rename to examples/gno.land/r/sys/vals/validators.gno From d1e3c96ae0ddf6194545d923552e97880fa3e849 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 09:56:08 +0200 Subject: [PATCH 30/71] Temporary rename --- examples/gno.land/r/sys/vals/{validators.gno => vals.gno} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/r/sys/vals/{validators.gno => vals.gno} (100%) diff --git a/examples/gno.land/r/sys/vals/validators.gno b/examples/gno.land/r/sys/vals/vals.gno similarity index 100% rename from examples/gno.land/r/sys/vals/validators.gno rename to examples/gno.land/r/sys/vals/vals.gno From 32ec0b69ab8b52bcea0ec7dbbcae49b68f547f27 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 10:30:28 +0200 Subject: [PATCH 31/71] Switch to testutils --- examples/gno.land/p/sys/vals/poc/gno.mod | 1 + examples/gno.land/p/sys/vals/poc/poc_test.gno | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod index 0263460b501..c872b7d6851 100644 --- a/examples/gno.land/p/sys/vals/poc/gno.mod +++ b/examples/gno.land/p/sys/vals/poc/gno.mod @@ -1,6 +1,7 @@ module gno.land/p/sys/vals/poc require ( + gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno index a6b63e97dc0..c67e3f53f90 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -3,7 +3,7 @@ package poc import ( "testing" - "std" + "gno.land/p/demo/testutils" "gno.land/p/demo/ufmt" "gno.land/p/sys/vals/types" @@ -15,7 +15,7 @@ func generateTestValidators(count int) []*types.Validator { for i := 0; i < count; i++ { val := &types.Validator{ - Address: std.Address(ufmt.Sprintf("%d", i)), + Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), PubKey: "public-key", VotingPower: 1, } @@ -33,7 +33,7 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { t.Parallel() var ( - proposalAddress = std.Address("caller") + proposalAddress = testutils.TestAddress("caller") proposalKey = "public-key" initialSet = generateTestValidators(1) @@ -56,7 +56,7 @@ func TestPoC_AddValidator(t *testing.T) { t.Parallel() var ( - proposalAddress = std.Address("caller") + proposalAddress = testutils.TestAddress("caller") proposalKey = "public-key" ) @@ -81,7 +81,7 @@ func TestPoC_RemoveValidator_Invalid(t *testing.T) { t.Parallel() var ( - proposalAddress = std.Address("caller") + proposalAddress = testutils.TestAddress("caller") initialSet = generateTestValidators(1) ) @@ -92,7 +92,7 @@ func TestPoC_RemoveValidator_Invalid(t *testing.T) { // Attempt to remove the validator testing.PanicsWithError(t, types.ErrValidatorMissing, func() { - p.RemoveValidator(std.Address("totally random")) + p.RemoveValidator(testutils.TestAddress("totally random")) }) }) } @@ -101,7 +101,7 @@ func TestPoC_RemoveValidator(t *testing.T) { t.Parallel() var ( - proposalAddress = std.Address("caller") + proposalAddress = testutils.TestAddress("caller") initialSet = generateTestValidators(1) ) From d81d300a5e2ae5b4fc598a691bfc6ff10319a1f3 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 16:21:32 +0200 Subject: [PATCH 32/71] Add voting power to AddValidator API --- examples/gno.land/p/sys/vals/poc/poc.gno | 15 +++++++++---- examples/gno.land/p/sys/vals/poc/poc_test.gno | 21 +++++++++++++++++-- examples/gno.land/p/sys/vals/types/types.gno | 2 +- .../gno.land/r/gov/dao/prop1_filetest.gno | 4 ++-- examples/gno.land/r/sys/vals/poc.gno | 2 +- examples/gno.land/r/sys/vals/vals.gno | 8 +++++-- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno index 2519cc1acd1..61bd2be75e0 100644 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -6,6 +6,8 @@ import ( "gno.land/p/sys/vals/types" ) +const errInvalidVotingPower = "invalid voting power" + // PoC specifies the Proof of Contribution validator set. // In order to become part of the set, users to be voted into // the validator set by a govdao proposal @@ -33,14 +35,14 @@ func NewPoC(opts ...Option) *PoC { return p } -func (p *PoC) AddValidator(address std.Address, pubKey string) *types.Validator { +func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *types.Validator { // Validate that the operation is a valid call - p.validateAdd(address) + p.validateAdd(address, power) v := &types.Validator{ Address: address, PubKey: pubKey, // TODO: in the future, verify the public key - VotingPower: 1, // in this PoC system, all new validators have the same voting power + VotingPower: power, } // Add the validator to the set @@ -51,11 +53,16 @@ func (p *PoC) AddValidator(address std.Address, pubKey string) *types.Validator } // validateAdd validates a validator add call -func (p *PoC) validateAdd(address std.Address) { +func (p *PoC) validateAdd(address std.Address, power uint64) { // Check if the validator is already in the set if p.IsValidator(address) { panic(types.ErrValidatorExists) } + + // Make sure the voting power > 0 + if power == 0 { + panic(errInvalidVotingPower) + } } func (p *PoC) RemoveValidator(address std.Address) *types.Validator { diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno index c67e3f53f90..dd932d2881d 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -47,7 +47,24 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { // Attempt to add the validator testing.PanicsWithError(t, types.ErrValidatorExists, func() { - p.AddValidator(proposalAddress, proposalKey) + p.AddValidator(proposalAddress, proposalKey, 1) + }) + }) + + t.Run("invalid voting power", func(t *testing.T) { + t.Parallel() + + var ( + proposalAddress = testutils.TestAddress("caller") + proposalKey = "public-key" + ) + + // Create the protocol with no initial set + p := NewPoC() + + // Attempt to add the validator + testing.PanicsWithError(t, errInvalidVotingPower, func() { + p.AddValidator(proposalAddress, proposalKey, 0) }) }) } @@ -65,7 +82,7 @@ func TestPoC_AddValidator(t *testing.T) { // Attempt to add the validator testing.NotPanics(t, func() { - p.AddValidator(proposalAddress, proposalKey) + p.AddValidator(proposalAddress, proposalKey, 1) }) // Make sure the validator is added diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types/types.gno index ae3a5710e8f..db240396d31 100644 --- a/examples/gno.land/p/sys/vals/types/types.gno +++ b/examples/gno.land/p/sys/vals/types/types.gno @@ -12,7 +12,7 @@ type Protocol interface { // TODO: This API is not ideal -- the address should be derived from // the public key, and not be passed in as such, but currently Gno // does not support crypto address derivation - AddValidator(address std.Address, pubKey string) *Validator + AddValidator(address std.Address, pubKey string, power uint64) *Validator // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method should error out diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index 83bed38a17e..cef2cf71ef6 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -93,6 +93,6 @@ func main() { // Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm // -- // Valset changes to apply: -// - g12345678 (1) -// - g000000000 (1) +// - g12345678 (10) +// - g000000000 (10) // - g000000000 (0) diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno index 736903695b2..fafd539ddea 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -38,7 +38,7 @@ func NewPropExecutor(changesFn func() []types.Validator) proposal.Executor { } // This change request is to add the validator - addValidator(change.Address, change.PubKey) + addValidator(change) } return nil diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 3e2d2620f81..17c89ec9efe 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -18,8 +18,12 @@ var v *vals // addValidator adds a new validator to the validator set. // If the validator is already present, the method errors out -func addValidator(address std.Address, pubKey string) { - if val := v.p.AddValidator(address, pubKey); val != nil { +func addValidator(validator types.Validator) { + if val := v.p.AddValidator( + validator.Address, + validator.PubKey, + validator.VotingPower, + ); val != nil { // Validator added, note the change v.changes = append(v.changes, types.Validator{ Address: val.Address, From 68534e74811c9bd672a91ce92f6996e2a9c531e2 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 16:58:28 +0200 Subject: [PATCH 33/71] Add GetValidator to Protocol API --- examples/gno.land/p/sys/vals/poc/poc.gno | 9 ++++ examples/gno.land/p/sys/vals/poc/poc_test.gno | 53 +++++++++++++++++++ examples/gno.land/p/sys/vals/types/types.gno | 3 ++ 3 files changed, 65 insertions(+) diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno index 61bd2be75e0..9b4906202bb 100644 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -95,6 +95,15 @@ func (p *PoC) IsValidator(address std.Address) bool { return exists } +func (p *PoC) GetValidator(address std.Address) types.Validator { + valIndex, exists := p.addressToValidatorIndex[address] + if !exists { + panic(types.ErrValidatorMissing) + } + + return *p.validators[valIndex] +} + func (p *PoC) GetValidators() []*types.Validator { return p.validators } diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno index dd932d2881d..e5799a0d462 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -137,3 +137,56 @@ func TestPoC_RemoveValidator(t *testing.T) { t.Fatal("address is validator") } } + +func TestPoC_GetValidator(t *testing.T) { + t.Parallel() + + t.Run("validator not in set", func(t *testing.T) { + t.Parallel() + + // Create the protocol with no initial set + p := NewPoC() + + // Attempt to get the voting power + testing.PanicsWithError(t, types.ErrValidatorMissing, func() { + p.GetValidator(testutils.TestAddress("caller")) + }) + }) + + t.Run("validator fetched", func(t *testing.T) { + t.Parallel() + + var ( + address = testutils.TestAddress("caller") + pubKey = "public-key" + votingPower = uint64(10) + + initialSet = generateTestValidators(1) + ) + + initialSet[0].Address = address + initialSet[0].PubKey = pubKey + initialSet[0].VotingPower = votingPower + + // Create the protocol with an initial set + p := NewPoC(WithInitialSet(initialSet)) + + // Get the validator + val := p.GetValidator(address) + + // Validate the address + if val.Address != address { + t.Fatal("invalid address") + } + + // Validate the voting power + if val.VotingPower != votingPower { + t.Fatal("invalid voting power") + } + + // Validate the public key + if val.PubKey != pubKey { + t.Fatal("invalid public key") + } + }) +} diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types/types.gno index db240396d31..3ac7e51acb3 100644 --- a/examples/gno.land/p/sys/vals/types/types.gno +++ b/examples/gno.land/p/sys/vals/types/types.gno @@ -22,6 +22,9 @@ type Protocol interface { // bech32 address is part of the validator set IsValidator(address std.Address) bool + // GetValidator returns the validator using the given address + GetValidator(address std.Address) Validator + // GetValidators returns the currently active validator set GetValidators() []*Validator } From 51414b0746c257cc3ff20d4c036961fc0bae8ad9 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 17:47:36 +0200 Subject: [PATCH 34/71] Switch to avl.Tree for map in PoC --- examples/gno.land/p/sys/vals/poc/gno.mod | 1 + examples/gno.land/p/sys/vals/poc/option.gno | 2 +- examples/gno.land/p/sys/vals/poc/poc.gno | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poc/gno.mod index c872b7d6851..089aacab982 100644 --- a/examples/gno.land/p/sys/vals/poc/gno.mod +++ b/examples/gno.land/p/sys/vals/poc/gno.mod @@ -1,6 +1,7 @@ module gno.land/p/sys/vals/poc require ( + gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest diff --git a/examples/gno.land/p/sys/vals/poc/option.gno b/examples/gno.land/p/sys/vals/poc/option.gno index ceccc193e7d..de01ac40b5d 100644 --- a/examples/gno.land/p/sys/vals/poc/option.gno +++ b/examples/gno.land/p/sys/vals/poc/option.gno @@ -9,7 +9,7 @@ func WithInitialSet(validators []*types.Validator) Option { return func(p *PoC) { for index, validator := range validators { p.validators = append(p.validators, validator) - p.addressToValidatorIndex[validator.Address] = index + p.addressToValidatorIndex.Set(validator.Address.String(), index) } } } diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno index 9b4906202bb..c92b865bf27 100644 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -3,6 +3,7 @@ package poc import ( "std" + "gno.land/p/demo/avl" "gno.land/p/sys/vals/types" ) @@ -16,7 +17,7 @@ type PoC struct { // This slice can never practically grow more than ~150 elements, // due to Tendermint's quadratic network complexity validators []*types.Validator - addressToValidatorIndex map[std.Address]int // address -> index + addressToValidatorIndex *avl.Tree // address -> index } // NewPoC creates a new empty Proof of Contribution validator set @@ -24,7 +25,7 @@ func NewPoC(opts ...Option) *PoC { // Create the empty set p := &PoC{ validators: make([]*types.Validator, 0), - addressToValidatorIndex: make(map[std.Address]int), + addressToValidatorIndex: avl.NewTree(), } // Apply the options @@ -46,7 +47,7 @@ func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *ty } // Add the validator to the set - p.addressToValidatorIndex[v.Address] = len(p.validators) + p.addressToValidatorIndex.Set(v.Address.String(), len(p.validators)) p.validators = append(p.validators, v) return v @@ -69,14 +70,17 @@ func (p *PoC) RemoveValidator(address std.Address) *types.Validator { // Validate that the operation is a valid call p.validateRemove(address) + addressStr := address.String() + // Fetch the validator index - index := p.addressToValidatorIndex[address] + indexRaw, _ := p.addressToValidatorIndex.Get(addressStr) + index := indexRaw.(int) // Remove the validator from the set validator := p.validators[index] p.validators = append(p.validators[:index], p.validators[index+1:]...) - delete(p.addressToValidatorIndex, address) + p.addressToValidatorIndex.Remove(addressStr) return validator } @@ -90,18 +94,18 @@ func (p *PoC) validateRemove(address std.Address) { } func (p *PoC) IsValidator(address std.Address) bool { - _, exists := p.addressToValidatorIndex[address] + _, exists := p.addressToValidatorIndex.Get(address.String()) return exists } func (p *PoC) GetValidator(address std.Address) types.Validator { - valIndex, exists := p.addressToValidatorIndex[address] + valIndexRaw, exists := p.addressToValidatorIndex.Get(address.String()) if !exists { panic(types.ErrValidatorMissing) } - return *p.validators[valIndex] + return *p.validators[valIndexRaw.(int)] } func (p *PoC) GetValidators() []*types.Validator { From 53de7381a100a1c1220fdc7e17aea69d2e95c9fa Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 17:57:15 +0200 Subject: [PATCH 35/71] Rename Protocol -> ValsetProtocol --- examples/gno.land/p/sys/vals/types/types.gno | 4 ++-- examples/gno.land/r/sys/vals/vals.gno | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types/types.gno index 3ac7e51acb3..937d01b6e6f 100644 --- a/examples/gno.land/p/sys/vals/types/types.gno +++ b/examples/gno.land/p/sys/vals/types/types.gno @@ -4,8 +4,8 @@ import ( "std" ) -// Protocol defines the validator set protocol (PoA / PoS / PoC / ?) -type Protocol interface { +// ValsetProtocol defines the validator set protocol (PoA / PoS / PoC / ?) +type ValsetProtocol interface { // AddValidator adds a new validator to the validator set. // If the validator is already present, the method should error out // diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 17c89ec9efe..f0fb58a4242 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -9,8 +9,8 @@ import ( // vals is the wrapper for the validator set protocol type vals struct { - p types.Protocol // p is the underlying validator set protocol - changes []types.Validator // changes are the set changes that happened between scrapes + p types.ValsetProtocol // p is the underlying validator set protocol + changes []types.Validator // changes are the set changes that happened between scrapes } // v holds the active on-chain validator set state From 8b6130a5929c8445b0e21999aaaf99d8ef8e6104 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 18:17:53 +0200 Subject: [PATCH 36/71] Temporary rename --- examples/gno.land/r/gov/dao/prop1_filetest.gno | 9 ++++----- examples/gno.land/r/sys/validators/doc.gno | 3 +++ examples/gno.land/r/sys/{vals => validators}/gno.mod | 2 +- examples/gno.land/r/sys/{vals => validators}/init.gno | 2 +- examples/gno.land/r/sys/{vals => validators}/poc.gno | 2 +- examples/gno.land/r/sys/{vals => validators}/vals.gno | 2 +- examples/gno.land/r/sys/vals/doc.gno | 3 --- 7 files changed, 11 insertions(+), 12 deletions(-) create mode 100644 examples/gno.land/r/sys/validators/doc.gno rename examples/gno.land/r/sys/{vals => validators}/gno.mod (80%) rename examples/gno.land/r/sys/{vals => validators}/init.gno (89%) rename examples/gno.land/r/sys/{vals => validators}/poc.gno (98%) rename examples/gno.land/r/sys/{vals => validators}/vals.gno (99%) delete mode 100644 examples/gno.land/r/sys/vals/doc.gno diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index cef2cf71ef6..d68e1b92166 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -12,11 +12,10 @@ import ( "gno.land/p/sys/vals/types" govdao "gno.land/r/gov/dao" - "gno.land/r/sys/vals" + "gno.land/r/sys/validators" ) func init() { - // Create the validators change proposal. changesFn := func() []types.Validator { return []types.Validator{ { @@ -39,7 +38,7 @@ func init() { // Wraps changesFn to emit a certified event only if executed from a // complete governance proposal process. - executor := vals.NewPropExecutor(changesFn) + executor := validators.NewPropExecutor(changesFn) // Create a proposal. // XXX: payment @@ -57,13 +56,13 @@ func main() { println("--") println(govdao.Render("1")) println("--") - println(vals.Render("")) + println(validators.Render("")) println("--") govdao.ExecuteProposal(1) println("--") println(govdao.Render("1")) println("--") - println(vals.Render("")) + println(validators.Render("")) } // Output: diff --git a/examples/gno.land/r/sys/validators/doc.gno b/examples/gno.land/r/sys/validators/doc.gno new file mode 100644 index 00000000000..d17b29ed110 --- /dev/null +++ b/examples/gno.land/r/sys/validators/doc.gno @@ -0,0 +1,3 @@ +// Package validators implements the on-chain validator set management through Proof of Contribution. +// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. +package validators diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/validators/gno.mod similarity index 80% rename from examples/gno.land/r/sys/vals/gno.mod rename to examples/gno.land/r/sys/validators/gno.mod index 221b2e3370c..7eb2348dade 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/validators/gno.mod @@ -1,4 +1,4 @@ -module gno.land/r/sys/vals +module gno.land/r/sys/validators require ( gno.land/p/gov/proposal v0.0.0-latest diff --git a/examples/gno.land/r/sys/vals/init.gno b/examples/gno.land/r/sys/validators/init.gno similarity index 89% rename from examples/gno.land/r/sys/vals/init.gno rename to examples/gno.land/r/sys/validators/init.gno index 5da5ad3d736..e6d019f4fdb 100644 --- a/examples/gno.land/r/sys/vals/init.gno +++ b/examples/gno.land/r/sys/validators/init.gno @@ -1,4 +1,4 @@ -package vals +package validators import ( "gno.land/p/sys/vals/poc" diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/validators/poc.gno similarity index 98% rename from examples/gno.land/r/sys/vals/poc.gno rename to examples/gno.land/r/sys/validators/poc.gno index fafd539ddea..beff1bd8c58 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/validators/poc.gno @@ -1,4 +1,4 @@ -package vals +package validators import ( "std" diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/validators/vals.gno similarity index 99% rename from examples/gno.land/r/sys/vals/vals.gno rename to examples/gno.land/r/sys/validators/vals.gno index f0fb58a4242..2066f5c8138 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/validators/vals.gno @@ -1,4 +1,4 @@ -package vals +package validators import ( "std" diff --git a/examples/gno.land/r/sys/vals/doc.gno b/examples/gno.land/r/sys/vals/doc.gno deleted file mode 100644 index 75405923376..00000000000 --- a/examples/gno.land/r/sys/vals/doc.gno +++ /dev/null @@ -1,3 +0,0 @@ -// Package vals implements the on-chain validator set management through Proof of Contribution. -// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. -package vals From 0860453d642670463dbe449ecd39eccddb543334 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 18:20:26 +0200 Subject: [PATCH 37/71] Temporary rename --- examples/gno.land/r/sys/{validators => vals}/doc.gno | 0 examples/gno.land/r/sys/{validators => vals}/gno.mod | 0 examples/gno.land/r/sys/{validators => vals}/init.gno | 0 examples/gno.land/r/sys/{validators => vals}/poc.gno | 0 examples/gno.land/r/sys/{validators => vals}/vals.gno | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/r/sys/{validators => vals}/doc.gno (100%) rename examples/gno.land/r/sys/{validators => vals}/gno.mod (100%) rename examples/gno.land/r/sys/{validators => vals}/init.gno (100%) rename examples/gno.land/r/sys/{validators => vals}/poc.gno (100%) rename examples/gno.land/r/sys/{validators => vals}/vals.gno (100%) diff --git a/examples/gno.land/r/sys/validators/doc.gno b/examples/gno.land/r/sys/vals/doc.gno similarity index 100% rename from examples/gno.land/r/sys/validators/doc.gno rename to examples/gno.land/r/sys/vals/doc.gno diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/vals/gno.mod similarity index 100% rename from examples/gno.land/r/sys/validators/gno.mod rename to examples/gno.land/r/sys/vals/gno.mod diff --git a/examples/gno.land/r/sys/validators/init.gno b/examples/gno.land/r/sys/vals/init.gno similarity index 100% rename from examples/gno.land/r/sys/validators/init.gno rename to examples/gno.land/r/sys/vals/init.gno diff --git a/examples/gno.land/r/sys/validators/poc.gno b/examples/gno.land/r/sys/vals/poc.gno similarity index 100% rename from examples/gno.land/r/sys/validators/poc.gno rename to examples/gno.land/r/sys/vals/poc.gno diff --git a/examples/gno.land/r/sys/validators/vals.gno b/examples/gno.land/r/sys/vals/vals.gno similarity index 100% rename from examples/gno.land/r/sys/validators/vals.gno rename to examples/gno.land/r/sys/vals/vals.gno From 68a2723983d5751790314387284af730432abde5 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 26 Jun 2024 18:20:56 +0200 Subject: [PATCH 38/71] Temporary rename --- examples/gno.land/r/gnoland/home/home.gno | 2 +- examples/gno.land/r/gnoland/home/home_filetest.gno | 2 +- examples/gno.land/r/gov/dao/prop1_filetest.gno | 8 ++++---- examples/gno.land/r/sys/vals/doc.gno | 4 ++-- examples/gno.land/r/sys/vals/gno.mod | 2 +- examples/gno.land/r/sys/vals/init.gno | 2 +- examples/gno.land/r/sys/vals/poc.gno | 2 +- examples/gno.land/r/sys/vals/vals.gno | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 07ce461020b..8d41f62c9d3 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -182,7 +182,7 @@ func packageStaffPicks() ui.Element { ui.BulletList{ ui.Link{URL: "r/sys/names"}, ui.Link{URL: "r/sys/rewards"}, - ui.Link{URL: "r/sys/validators"}, + ui.Link{URL: "r/sys/vals"}, }, }, { ui.H4("[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)"), diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 919f8dd4fbc..4cb7664d14f 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -118,7 +118,7 @@ func main() { // // - [r/sys/names](r/sys/names) // - [r/sys/rewards](r/sys/rewards) -// - [r/sys/validators](r/sys/validators) +// - [r/sys/vals](r/sys/vals) // // //
diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index d68e1b92166..17472169dd0 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -12,7 +12,7 @@ import ( "gno.land/p/sys/vals/types" govdao "gno.land/r/gov/dao" - "gno.land/r/sys/validators" + "gno.land/r/sys/vals" ) func init() { @@ -38,7 +38,7 @@ func init() { // Wraps changesFn to emit a certified event only if executed from a // complete governance proposal process. - executor := validators.NewPropExecutor(changesFn) + executor := vals.NewPropExecutor(changesFn) // Create a proposal. // XXX: payment @@ -56,13 +56,13 @@ func main() { println("--") println(govdao.Render("1")) println("--") - println(validators.Render("")) + println(vals.Render("")) println("--") govdao.ExecuteProposal(1) println("--") println(govdao.Render("1")) println("--") - println(validators.Render("")) + println(vals.Render("")) } // Output: diff --git a/examples/gno.land/r/sys/vals/doc.gno b/examples/gno.land/r/sys/vals/doc.gno index d17b29ed110..75405923376 100644 --- a/examples/gno.land/r/sys/vals/doc.gno +++ b/examples/gno.land/r/sys/vals/doc.gno @@ -1,3 +1,3 @@ -// Package validators implements the on-chain validator set management through Proof of Contribution. +// Package vals implements the on-chain validator set management through Proof of Contribution. // The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. -package validators +package vals diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index 7eb2348dade..221b2e3370c 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -1,4 +1,4 @@ -module gno.land/r/sys/validators +module gno.land/r/sys/vals require ( gno.land/p/gov/proposal v0.0.0-latest diff --git a/examples/gno.land/r/sys/vals/init.gno b/examples/gno.land/r/sys/vals/init.gno index e6d019f4fdb..5da5ad3d736 100644 --- a/examples/gno.land/r/sys/vals/init.gno +++ b/examples/gno.land/r/sys/vals/init.gno @@ -1,4 +1,4 @@ -package validators +package vals import ( "gno.land/p/sys/vals/poc" diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno index beff1bd8c58..fafd539ddea 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -1,4 +1,4 @@ -package validators +package vals import ( "std" diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 2066f5c8138..f0fb58a4242 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -1,4 +1,4 @@ -package validators +package vals import ( "std" From ed2f7a668666d4a048e50d1c13b450d6724da2e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Thu, 27 Jun 2024 13:38:50 +0200 Subject: [PATCH 39/71] Update examples/gno.land/r/sys/vals/vals.gno Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- examples/gno.land/r/sys/vals/vals.gno | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index f0fb58a4242..72ec5bad5f5 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -25,11 +25,7 @@ func addValidator(validator types.Validator) { validator.VotingPower, ); val != nil { // Validator added, note the change - v.changes = append(v.changes, types.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: val.VotingPower, - }) + v.changes = append(v.changes, *val) // Emit the validator set change std.Emit(types.ValidatorAddedEvent) From 3a7fd9c2eadb1f1050ca76c9b2d77441b6f52ea7 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Thu, 27 Jun 2024 13:43:27 +0200 Subject: [PATCH 40/71] Simplify verify methods --- examples/gno.land/p/sys/vals/poc/poc.gno | 36 +++++++++--------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno index c92b865bf27..1fcb28492a1 100644 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -38,7 +38,15 @@ func NewPoC(opts ...Option) *PoC { func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *types.Validator { // Validate that the operation is a valid call - p.validateAdd(address, power) + // Check if the validator is already in the set + if p.IsValidator(address) { + panic(types.ErrValidatorExists) + } + + // Make sure the voting power > 0 + if power == 0 { + panic(errInvalidVotingPower) + } v := &types.Validator{ Address: address, @@ -53,22 +61,12 @@ func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *ty return v } -// validateAdd validates a validator add call -func (p *PoC) validateAdd(address std.Address, power uint64) { - // Check if the validator is already in the set - if p.IsValidator(address) { - panic(types.ErrValidatorExists) - } - - // Make sure the voting power > 0 - if power == 0 { - panic(errInvalidVotingPower) - } -} - func (p *PoC) RemoveValidator(address std.Address) *types.Validator { // Validate that the operation is a valid call - p.validateRemove(address) + // Check if the address is a validator + if !p.IsValidator(address) { + panic(types.ErrValidatorMissing) + } addressStr := address.String() @@ -85,14 +83,6 @@ func (p *PoC) RemoveValidator(address std.Address) *types.Validator { return validator } -// validateRemove validates a validator remove call -func (p *PoC) validateRemove(address std.Address) { - // Check if the address is a validator - if !p.IsValidator(address) { - panic(types.ErrValidatorMissing) - } -} - func (p *PoC) IsValidator(address std.Address) bool { _, exists := p.addressToValidatorIndex.Get(address.String()) From 96a6d7af36fe8f6e589340f8c5e9d8f40801774f Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 28 Jun 2024 16:28:01 +0200 Subject: [PATCH 41/71] Change event parsing for vals --- gno.land/pkg/gnoland/app.go | 98 ++++++++++++++++++++++++++++++-- gno.land/pkg/gnoland/events.go | 3 +- gno.land/pkg/gnoland/vals.go | 100 +++++++-------------------------- gnovm/tests/imports.go | 6 +- 4 files changed, 116 insertions(+), 91 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index a8307c250a7..d953493bd68 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -4,11 +4,13 @@ import ( "fmt" "log/slog" "path/filepath" + "strconv" "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/config" + "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/log" @@ -110,13 +112,13 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { ) // Set up the event collector - c := newCollector[abci.ValidatorUpdate]( + c := newCollector[validatorUpdate]( cfg.EventSwitch, // global event switch filled by the node validatorEventFilter, // filter fn that keeps the collector valid ) // Set EndBlocker - baseApp.SetEndBlocker(EndBlocker(c)) + baseApp.SetEndBlocker(EndBlocker(c, vmKpr, baseApp.Logger())) // Set a handler Route. baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) @@ -220,10 +222,96 @@ func InitChainer( // EndBlocker defines the logic executed after every block. // Currently, it parses events that happened during execution to calculate // validator set changes -func EndBlocker(collector *collector[abci.ValidatorUpdate]) func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return func(_ sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { +func EndBlocker( + collector *collector[validatorUpdate], + vmKeeper vm.VMKeeperI, + logger *slog.Logger, +) func( + ctx sdk.Context, + req abci.RequestEndBlock, +) abci.ResponseEndBlock { + return func(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { + // Check if there was a valset change + if len(collector.getEvents()) == 0 { + // No valset updates + return abci.ResponseEndBlock{} + } + + // Run the VM to get the updates from the chain + msg := vm.MsgCall{ + Caller: crypto.Address{}, // Zero address + PkgPath: valRealm, + Func: valChangesFn, + } + + response, err := vmKeeper.Call(ctx, msg) + if err != nil { + logger.Error("unable to call VM during EndBlocker", "err", err) + + return abci.ResponseEndBlock{} + } + + // Extract the updates from the VM response + updates, err := extractUpdatesFromResponse(response) + if err != nil { + logger.Error("unable to extract updates from response", "err", err) + + return abci.ResponseEndBlock{} + } + return abci.ResponseEndBlock{ - ValidatorUpdates: collector.getEvents(), + ValidatorUpdates: updates, } } } + +// extractUpdatesFromResponse extracts the validator set updates +// from the VM response. +// +// This method is not ideal, but currently there is no mechanism +// in place to parse typed VM responses +func extractUpdatesFromResponse(response string) ([]abci.ValidatorUpdate, error) { + // Find the submatches + matches := valRegexp.FindAllStringSubmatch(response, -1) + if len(matches) == 0 { + // No changes to extract + return nil, nil + } + + updates := make([]abci.ValidatorUpdate, 0, len(matches)) + for _, match := range matches { + var ( + addressRaw = match[1] + pubKeyRaw = match[2] + powerRaw = match[3] + ) + + // Parse the address + address, err := crypto.AddressFromBech32(addressRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse address, %w", err) + } + + // Parse the public key + pubKey, err := crypto.PubKeyFromBech32(pubKeyRaw) + if err != nil { + return nil, fmt.Errorf("unable to parse public key, %w", err) + } + + // Parse the voting power + power, err := strconv.ParseInt(powerRaw, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse voting power, %w", err) + } + + update := abci.ValidatorUpdate{ + Address: address, + PubKey: pubKey, + Power: power, + } + + updates = append(updates, update) + } + + return updates, nil +} diff --git a/gno.land/pkg/gnoland/events.go b/gno.land/pkg/gnoland/events.go index 74cbad5aaac..8bc9b788475 100644 --- a/gno.land/pkg/gnoland/events.go +++ b/gno.land/pkg/gnoland/events.go @@ -1,7 +1,6 @@ package gnoland import ( - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/events" "github.com/rs/xid" ) @@ -9,7 +8,7 @@ import ( // eventType encompasses all event types // that can appear in the collector type eventType interface { - abci.ValidatorUpdate + validatorUpdate } // filterFn is the filter method for incoming events diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go index 5eac2435ce1..831cd327fa3 100644 --- a/gno.land/pkg/gnoland/vals.go +++ b/gno.land/pkg/gnoland/vals.go @@ -1,74 +1,38 @@ package gnoland import ( - "fmt" - "strconv" + "regexp" gnovm "github.com/gnolang/gno/gnovm/stdlibs/std" - abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/events" ) const ( - valRealm = "gno.land/r/sys/vals" + valRealm = "gno.land/r/sys/vals" + valChangesFn = "getChanges" validatorAddedEvent = "ValidatorAdded" validatorRemovedEvent = "ValidatorRemoved" - - addressEventKey = "address" - pubKeyEventKey = "pub_key" - votingPowerEventKey = "voting_power" ) -// validatorEventFilter -func validatorEventFilter(event events.Event) []abci.ValidatorUpdate { +var valRegexp = regexp.MustCompile(`{\("([^"]*)"\s[^)]+\),\("((?:[^"]|\\")*)"\s[^)]+\),\((\d+)\s[^)]+\)}`) + +// validatorUpdate is a type being used for "notifying" +// that a validator change happened on-chain. The events from `r/sys/vals` +// do not pass data related to validator add / remove instances (who, what, how) +type validatorUpdate struct{} + +// validatorEventFilter filters the given event to determine if it +// is tied to a validator update +func validatorEventFilter(event events.Event) []validatorUpdate { // Make sure the event is a new TX event txResult, ok := event.(types.EventTx) if !ok { return nil } - // extractValUpdate parses the event attributes and extracts the relevant - // validator change data - extractValUpdate := func(attributes []gnovm.GnoEventAttribute) (*abci.ValidatorUpdate, error) { - // Extract the event attributes - attrs := extractEventAttributes(attributes) - - var ( - addressRaw = attrs[addressEventKey] - pubKeyRaw = attrs[pubKeyEventKey] - votingPowerRaw = attrs[votingPowerEventKey] - ) - - // Parse the address - address, err := crypto.AddressFromBech32(addressRaw) - if err != nil { - return nil, fmt.Errorf("unable to parse address, %w", err) - } - - // Parse the public key - pubKey, err := crypto.PubKeyFromBech32(pubKeyRaw) - if err != nil { - return nil, fmt.Errorf("unable to parse public key, %w", err) - } - - // Parse the voting power - votingPower, err := strconv.Atoi(votingPowerRaw) - if err != nil { - return nil, fmt.Errorf("unable to parse voting power, %w", err) - } - - return &abci.ValidatorUpdate{ - Address: address, - PubKey: pubKey, - Power: int64(votingPower), - }, nil - } - - // Extract the validator change events - valUpdates := make([]abci.ValidatorUpdate, 0) + // Make sure an add / remove event happened for _, ev := range txResult.Result.Response.Events { // Make sure the event is a GnoVM event gnoEv, ok := ev.(gnovm.GnoEvent) @@ -83,40 +47,14 @@ func validatorEventFilter(event events.Event) []abci.ValidatorUpdate { // Make sure the event is either an add / remove switch gnoEv.Type { - case validatorAddedEvent: - update, err := extractValUpdate(gnoEv.Attributes) - if err != nil { - continue - } - - valUpdates = append(valUpdates, *update) - case validatorRemovedEvent: - update, err := extractValUpdate(gnoEv.Attributes) - if err != nil { - continue - } - - // Validator updates that have Power == 0 - // are considered to be "remove" signals - update.Power = 0 - - valUpdates = append(valUpdates, *update) + case validatorAddedEvent, validatorRemovedEvent: + // We don't pass data around with the events, but a single + // notification is enough to "trigger" a VM scrape + return []validatorUpdate{} default: continue } } - return valUpdates -} - -// extractEventAttributes generates an attribute map from -// the gno event attributes, for quick lookup -func extractEventAttributes(evAttrs []gnovm.GnoEventAttribute) map[string]string { - attrs := make(map[string]string, len(evAttrs)) - - for _, attr := range evAttrs { - attrs[attr.Key] = attr.Value - } - - return attrs + return nil } diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 86c81be9a18..b40d5f95af4 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -271,7 +271,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) // make native rand same as gno rand. - rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec + rnd := rand.New(rand.NewPCG(0, 0)) pkg.DefineGoNativeValue("IntN", rnd.IntN) pkg.DefineGoNativeValue("Uint32", rnd.Uint32) return pkg, pkg.NewPackage() @@ -464,7 +464,7 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) { } } -//---------------------------------------- +// ---------------------------------------- type dummyReader struct{} @@ -475,7 +475,7 @@ func (*dummyReader) Read(b []byte) (n int, err error) { return len(b), nil } -//---------------------------------------- +// ---------------------------------------- type TestReport struct { Name string From 4de53fff54423ff556ff972cc597149f1ea47381 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 28 Jun 2024 16:29:39 +0200 Subject: [PATCH 42/71] Revert linter fix --- gnovm/tests/imports.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index b40d5f95af4..9c502e06a9b 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -271,7 +271,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) // make native rand same as gno rand. - rnd := rand.New(rand.NewPCG(0, 0)) + rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec pkg.DefineGoNativeValue("IntN", rnd.IntN) pkg.DefineGoNativeValue("Uint32", rnd.Uint32) return pkg, pkg.NewPackage() From b5e10f1e74faff4244f6a19137ac58c530450036 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 1 Jul 2024 12:20:14 +0200 Subject: [PATCH 43/71] Use AVL to manage valsets --- examples/gno.land/p/sys/vals/poc/option.gno | 5 +- examples/gno.land/p/sys/vals/poc/poc.gno | 45 +++++++-------- examples/gno.land/p/sys/vals/poc/poc_test.gno | 56 ++++++++++++++++++- 3 files changed, 77 insertions(+), 29 deletions(-) diff --git a/examples/gno.land/p/sys/vals/poc/option.gno b/examples/gno.land/p/sys/vals/poc/option.gno index de01ac40b5d..28d2105991d 100644 --- a/examples/gno.land/p/sys/vals/poc/option.gno +++ b/examples/gno.land/p/sys/vals/poc/option.gno @@ -7,9 +7,8 @@ type Option func(*PoC) // WithInitialSet sets the initial PoA validator set func WithInitialSet(validators []*types.Validator) Option { return func(p *PoC) { - for index, validator := range validators { - p.validators = append(p.validators, validator) - p.addressToValidatorIndex.Set(validator.Address.String(), index) + for _, validator := range validators { + p.validators.Set(validator.Address.String(), validator) } } } diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno index 1fcb28492a1..db7b2c61c69 100644 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ b/examples/gno.land/p/sys/vals/poc/poc.gno @@ -13,19 +13,14 @@ const errInvalidVotingPower = "invalid voting power" // In order to become part of the set, users to be voted into // the validator set by a govdao proposal type PoC struct { - // validators holds the current validator set. - // This slice can never practically grow more than ~150 elements, - // due to Tendermint's quadratic network complexity - validators []*types.Validator - addressToValidatorIndex *avl.Tree // address -> index + validators *avl.Tree // std.Address -> *types.Validator } // NewPoC creates a new empty Proof of Contribution validator set func NewPoC(opts ...Option) *PoC { // Create the empty set p := &PoC{ - validators: make([]*types.Validator, 0), - addressToValidatorIndex: avl.NewTree(), + validators: avl.NewTree(), } // Apply the options @@ -37,7 +32,7 @@ func NewPoC(opts ...Option) *PoC { } func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *types.Validator { - // Validate that the operation is a valid call + // Validate that the operation is a valid call. // Check if the validator is already in the set if p.IsValidator(address) { panic(types.ErrValidatorExists) @@ -55,8 +50,7 @@ func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *ty } // Add the validator to the set - p.addressToValidatorIndex.Set(v.Address.String(), len(p.validators)) - p.validators = append(p.validators, v) + p.validators.Set(address.String(), v) return v } @@ -68,36 +62,39 @@ func (p *PoC) RemoveValidator(address std.Address) *types.Validator { panic(types.ErrValidatorMissing) } - addressStr := address.String() - - // Fetch the validator index - indexRaw, _ := p.addressToValidatorIndex.Get(addressStr) - index := indexRaw.(int) + // Fetch the validator + validator := p.GetValidator(address) // Remove the validator from the set - validator := p.validators[index] - p.validators = append(p.validators[:index], p.validators[index+1:]...) - - p.addressToValidatorIndex.Remove(addressStr) + p.validators.Remove(address.String()) - return validator + return &validator } func (p *PoC) IsValidator(address std.Address) bool { - _, exists := p.addressToValidatorIndex.Get(address.String()) + _, exists := p.validators.Get(address.String()) return exists } func (p *PoC) GetValidator(address std.Address) types.Validator { - valIndexRaw, exists := p.addressToValidatorIndex.Get(address.String()) + validatorRaw, exists := p.validators.Get(address.String()) if !exists { panic(types.ErrValidatorMissing) } - return *p.validators[valIndexRaw.(int)] + return *(validatorRaw.(*types.Validator)) } func (p *PoC) GetValidators() []*types.Validator { - return p.validators + validators := make([]*types.Validator, 0, p.validators.Size()) + + p.validators.Iterate("", "", func(_ string, value interface{}) bool { + validator := value.(*types.Validator) + validators = append(validators, validator) + + return false + }) + + return validators } diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poc/poc_test.gno index e5799a0d462..e1a993424e3 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poc/poc_test.gno @@ -86,7 +86,7 @@ func TestPoC_AddValidator(t *testing.T) { }) // Make sure the validator is added - if !p.IsValidator(proposalAddress) || len(p.validators) != 1 { + if !p.IsValidator(proposalAddress) || p.validators.Size() != 1 { t.Fatal("address is not validator") } } @@ -133,7 +133,7 @@ func TestPoC_RemoveValidator(t *testing.T) { }) // Make sure the validator is removed - if p.IsValidator(proposalAddress) || len(p.validators) != 0 { + if p.IsValidator(proposalAddress) || p.validators.Size() != 0 { t.Fatal("address is validator") } } @@ -190,3 +190,55 @@ func TestPoC_GetValidator(t *testing.T) { } }) } + +func TestPoC_GetValidators(t *testing.T) { + t.Parallel() + + t.Run("empty set", func(t *testing.T) { + t.Parallel() + + // Create the protocol with no initial set + p := NewPoC() + + // Attempt to get the voting power + vals := p.GetValidators() + + if len(vals) != 0 { + t.Fatal("validator set is not empty") + } + }) + + t.Run("validator set fetched", func(t *testing.T) { + t.Parallel() + + initialSet := generateTestValidators(10) + + // Create the protocol with an initial set + p := NewPoC(WithInitialSet(initialSet)) + + // Get the validator set + vals := p.GetValidators() + + if len(vals) != len(initialSet) { + t.Fatal("returned validator set mismatch") + } + + for _, val := range vals { + for _, initialVal := range initialSet { + if val.Address != initialVal.Address { + continue + } + + // Validate the voting power + if val.VotingPower != initialVal.VotingPower { + t.Fatal("invalid voting power") + } + + // Validate the public key + if val.PubKey != initialVal.PubKey { + t.Fatal("invalid public key") + } + } + } + }) +} From 122e178c7adcacfddcd8eb9262ff051e8e896147 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 1 Jul 2024 13:25:07 +0200 Subject: [PATCH 44/71] Simplify ValsetProtocol API --- .../gno.land/p/sys/vals/{poc => poa}/gno.mod | 2 +- .../p/sys/vals/{poc => poa}/option.gno | 8 +- examples/gno.land/p/sys/vals/poa/poa.gno | 106 ++++++++++++++++++ .../{poc/poc_test.gno => poa/poa_test.gno} | 71 ++++++------ examples/gno.land/p/sys/vals/poc/poc.gno | 100 ----------------- examples/gno.land/p/sys/vals/types/types.gno | 18 ++- examples/gno.land/r/sys/vals/gno.mod | 2 +- examples/gno.land/r/sys/vals/init.gno | 4 +- examples/gno.land/r/sys/vals/poc.gno | 2 +- examples/gno.land/r/sys/vals/vals.gno | 19 +++- gnovm/stdlibs/testing/testing.gno | 26 +++++ 11 files changed, 195 insertions(+), 163 deletions(-) rename examples/gno.land/p/sys/vals/{poc => poa}/gno.mod (84%) rename examples/gno.land/p/sys/vals/{poc => poa}/option.gno (62%) create mode 100644 examples/gno.land/p/sys/vals/poa/poa.gno rename examples/gno.land/p/sys/vals/{poc/poc_test.gno => poa/poa_test.gno} (74%) delete mode 100644 examples/gno.land/p/sys/vals/poc/poc.gno diff --git a/examples/gno.land/p/sys/vals/poc/gno.mod b/examples/gno.land/p/sys/vals/poa/gno.mod similarity index 84% rename from examples/gno.land/p/sys/vals/poc/gno.mod rename to examples/gno.land/p/sys/vals/poa/gno.mod index 089aacab982..cec27e50c3c 100644 --- a/examples/gno.land/p/sys/vals/poc/gno.mod +++ b/examples/gno.land/p/sys/vals/poa/gno.mod @@ -1,4 +1,4 @@ -module gno.land/p/sys/vals/poc +module gno.land/p/sys/vals/poa require ( gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/sys/vals/poc/option.gno b/examples/gno.land/p/sys/vals/poa/option.gno similarity index 62% rename from examples/gno.land/p/sys/vals/poc/option.gno rename to examples/gno.land/p/sys/vals/poa/option.gno index 28d2105991d..900eee5a77e 100644 --- a/examples/gno.land/p/sys/vals/poc/option.gno +++ b/examples/gno.land/p/sys/vals/poa/option.gno @@ -1,12 +1,12 @@ -package poc +package poa import "gno.land/p/sys/vals/types" -type Option func(*PoC) +type Option func(*PoA) // WithInitialSet sets the initial PoA validator set -func WithInitialSet(validators []*types.Validator) Option { - return func(p *PoC) { +func WithInitialSet(validators []types.Validator) Option { + return func(p *PoA) { for _, validator := range validators { p.validators.Set(validator.Address.String(), validator) } diff --git a/examples/gno.land/p/sys/vals/poa/poa.gno b/examples/gno.land/p/sys/vals/poa/poa.gno new file mode 100644 index 00000000000..44362e2bec1 --- /dev/null +++ b/examples/gno.land/p/sys/vals/poa/poa.gno @@ -0,0 +1,106 @@ +package poa + +import ( + "errors" + "std" + + "gno.land/p/demo/avl" + "gno.land/p/sys/vals/types" +) + +var ErrInvalidVotingPower = errors.New("invalid voting power") + +// PoA specifies the Proof of Authority validator set, with simple add / remove constraints. +// +// To add: +// - proposed validator must not be part of the set already +// - proposed validator voting power must be > 0 +// +// To remove: +// - proposed validator must be part of the set already +type PoA struct { + validators *avl.Tree // std.Address -> types.Validator +} + +// NewPoA creates a new empty Proof of Authority validator set +func NewPoA(opts ...Option) *PoA { + // Create the empty set + p := &PoA{ + validators: avl.NewTree(), + } + + // Apply the options + for _, opt := range opts { + opt(p) + } + + return p +} + +func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*types.Validator, error) { + // Validate that the operation is a valid call. + // Check if the validator is already in the set + if p.IsValidator(address) { + return nil, types.ErrValidatorExists + } + + // Make sure the voting power > 0 + if power == 0 { + return nil, ErrInvalidVotingPower + } + + v := types.Validator{ + Address: address, + PubKey: pubKey, // TODO: in the future, verify the public key + VotingPower: power, + } + + // Add the validator to the set + p.validators.Set(address.String(), v) + + return &v, nil +} + +func (p *PoA) RemoveValidator(address std.Address) (*types.Validator, error) { + // Validate that the operation is a valid call + // Fetch the validator + validator, err := p.GetValidator(address) + if err != nil { + return nil, err + } + + // Remove the validator from the set + p.validators.Remove(address.String()) + + return validator, nil +} + +func (p *PoA) IsValidator(address std.Address) bool { + _, exists := p.validators.Get(address.String()) + + return exists +} + +func (p *PoA) GetValidator(address std.Address) (*types.Validator, error) { + validatorRaw, exists := p.validators.Get(address.String()) + if !exists { + return nil, types.ErrValidatorMissing + } + + validator := validatorRaw.(types.Validator) + + return &validator, nil +} + +func (p *PoA) GetValidators() []types.Validator { + validators := make([]types.Validator, 0, p.validators.Size()) + + p.validators.Iterate("", "", func(_ string, value interface{}) bool { + validator := value.(types.Validator) + validators = append(validators, validator) + + return false + }) + + return validators +} diff --git a/examples/gno.land/p/sys/vals/poc/poc_test.gno b/examples/gno.land/p/sys/vals/poa/poa_test.gno similarity index 74% rename from examples/gno.land/p/sys/vals/poc/poc_test.gno rename to examples/gno.land/p/sys/vals/poa/poa_test.gno index e1a993424e3..5c9430cd3f9 100644 --- a/examples/gno.land/p/sys/vals/poc/poc_test.gno +++ b/examples/gno.land/p/sys/vals/poa/poa_test.gno @@ -1,4 +1,4 @@ -package poc +package poa import ( "testing" @@ -10,11 +10,11 @@ import ( ) // generateTestValidators generates a dummy validator set -func generateTestValidators(count int) []*types.Validator { - vals := make([]*types.Validator, 0, count) +func generateTestValidators(count int) []types.Validator { + vals := make([]types.Validator, 0, count) for i := 0; i < count; i++ { - val := &types.Validator{ + val := types.Validator{ Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), PubKey: "public-key", VotingPower: 1, @@ -26,7 +26,7 @@ func generateTestValidators(count int) []*types.Validator { return vals } -func TestPoC_AddValidator_Invalid(t *testing.T) { +func TestPoA_AddValidator_Invalid(t *testing.T) { t.Parallel() t.Run("validator already in set", func(t *testing.T) { @@ -43,12 +43,11 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { initialSet[0].PubKey = proposalKey // Create the protocol with an initial set - p := NewPoC(WithInitialSet(initialSet)) + p := NewPoA(WithInitialSet(initialSet)) // Attempt to add the validator - testing.PanicsWithError(t, types.ErrValidatorExists, func() { - p.AddValidator(proposalAddress, proposalKey, 1) - }) + _, err := p.AddValidator(proposalAddress, proposalKey, 1) + testing.ErrorIs(t, err, types.ErrValidatorExists) }) t.Run("invalid voting power", func(t *testing.T) { @@ -60,16 +59,15 @@ func TestPoC_AddValidator_Invalid(t *testing.T) { ) // Create the protocol with no initial set - p := NewPoC() + p := NewPoA() // Attempt to add the validator - testing.PanicsWithError(t, errInvalidVotingPower, func() { - p.AddValidator(proposalAddress, proposalKey, 0) - }) + _, err := p.AddValidator(proposalAddress, proposalKey, 0) + testing.ErrorIs(t, err, ErrInvalidVotingPower) }) } -func TestPoC_AddValidator(t *testing.T) { +func TestPoA_AddValidator(t *testing.T) { t.Parallel() var ( @@ -78,12 +76,11 @@ func TestPoC_AddValidator(t *testing.T) { ) // Create the protocol with no initial set - p := NewPoC() + p := NewPoA() // Attempt to add the validator - testing.NotPanics(t, func() { - p.AddValidator(proposalAddress, proposalKey, 1) - }) + _, err := p.AddValidator(proposalAddress, proposalKey, 1) + testing.NoError(t, err) // Make sure the validator is added if !p.IsValidator(proposalAddress) || p.validators.Size() != 1 { @@ -91,7 +88,7 @@ func TestPoC_AddValidator(t *testing.T) { } } -func TestPoC_RemoveValidator_Invalid(t *testing.T) { +func TestPoA_RemoveValidator_Invalid(t *testing.T) { t.Parallel() t.Run("proposed removal not in set", func(t *testing.T) { @@ -105,16 +102,15 @@ func TestPoC_RemoveValidator_Invalid(t *testing.T) { initialSet[0].Address = proposalAddress // Create the protocol with an initial set - p := NewPoC(WithInitialSet(initialSet)) + p := NewPoA(WithInitialSet(initialSet)) // Attempt to remove the validator - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { - p.RemoveValidator(testutils.TestAddress("totally random")) - }) + _, err := p.RemoveValidator(testutils.TestAddress("totally random")) + testing.ErrorIs(t, err, types.ErrValidatorMissing) }) } -func TestPoC_RemoveValidator(t *testing.T) { +func TestPoA_RemoveValidator(t *testing.T) { t.Parallel() var ( @@ -125,12 +121,11 @@ func TestPoC_RemoveValidator(t *testing.T) { initialSet[0].Address = proposalAddress // Create the protocol with an initial set - p := NewPoC(WithInitialSet(initialSet)) + p := NewPoA(WithInitialSet(initialSet)) // Attempt to remove the validator - testing.NotPanics(t, func() { - p.RemoveValidator(proposalAddress) - }) + _, err := p.RemoveValidator(proposalAddress) + testing.NoError(t, err) // Make sure the validator is removed if p.IsValidator(proposalAddress) || p.validators.Size() != 0 { @@ -138,19 +133,18 @@ func TestPoC_RemoveValidator(t *testing.T) { } } -func TestPoC_GetValidator(t *testing.T) { +func TestPoA_GetValidator(t *testing.T) { t.Parallel() t.Run("validator not in set", func(t *testing.T) { t.Parallel() // Create the protocol with no initial set - p := NewPoC() + p := NewPoA() // Attempt to get the voting power - testing.PanicsWithError(t, types.ErrValidatorMissing, func() { - p.GetValidator(testutils.TestAddress("caller")) - }) + _, err := p.GetValidator(testutils.TestAddress("caller")) + testing.ErrorIs(t, err, types.ErrValidatorMissing) }) t.Run("validator fetched", func(t *testing.T) { @@ -169,10 +163,11 @@ func TestPoC_GetValidator(t *testing.T) { initialSet[0].VotingPower = votingPower // Create the protocol with an initial set - p := NewPoC(WithInitialSet(initialSet)) + p := NewPoA(WithInitialSet(initialSet)) // Get the validator - val := p.GetValidator(address) + val, err := p.GetValidator(address) + testing.NoError(t, err) // Validate the address if val.Address != address { @@ -191,14 +186,14 @@ func TestPoC_GetValidator(t *testing.T) { }) } -func TestPoC_GetValidators(t *testing.T) { +func TestPoA_GetValidators(t *testing.T) { t.Parallel() t.Run("empty set", func(t *testing.T) { t.Parallel() // Create the protocol with no initial set - p := NewPoC() + p := NewPoA() // Attempt to get the voting power vals := p.GetValidators() @@ -214,7 +209,7 @@ func TestPoC_GetValidators(t *testing.T) { initialSet := generateTestValidators(10) // Create the protocol with an initial set - p := NewPoC(WithInitialSet(initialSet)) + p := NewPoA(WithInitialSet(initialSet)) // Get the validator set vals := p.GetValidators() diff --git a/examples/gno.land/p/sys/vals/poc/poc.gno b/examples/gno.land/p/sys/vals/poc/poc.gno deleted file mode 100644 index db7b2c61c69..00000000000 --- a/examples/gno.land/p/sys/vals/poc/poc.gno +++ /dev/null @@ -1,100 +0,0 @@ -package poc - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/sys/vals/types" -) - -const errInvalidVotingPower = "invalid voting power" - -// PoC specifies the Proof of Contribution validator set. -// In order to become part of the set, users to be voted into -// the validator set by a govdao proposal -type PoC struct { - validators *avl.Tree // std.Address -> *types.Validator -} - -// NewPoC creates a new empty Proof of Contribution validator set -func NewPoC(opts ...Option) *PoC { - // Create the empty set - p := &PoC{ - validators: avl.NewTree(), - } - - // Apply the options - for _, opt := range opts { - opt(p) - } - - return p -} - -func (p *PoC) AddValidator(address std.Address, pubKey string, power uint64) *types.Validator { - // Validate that the operation is a valid call. - // Check if the validator is already in the set - if p.IsValidator(address) { - panic(types.ErrValidatorExists) - } - - // Make sure the voting power > 0 - if power == 0 { - panic(errInvalidVotingPower) - } - - v := &types.Validator{ - Address: address, - PubKey: pubKey, // TODO: in the future, verify the public key - VotingPower: power, - } - - // Add the validator to the set - p.validators.Set(address.String(), v) - - return v -} - -func (p *PoC) RemoveValidator(address std.Address) *types.Validator { - // Validate that the operation is a valid call - // Check if the address is a validator - if !p.IsValidator(address) { - panic(types.ErrValidatorMissing) - } - - // Fetch the validator - validator := p.GetValidator(address) - - // Remove the validator from the set - p.validators.Remove(address.String()) - - return &validator -} - -func (p *PoC) IsValidator(address std.Address) bool { - _, exists := p.validators.Get(address.String()) - - return exists -} - -func (p *PoC) GetValidator(address std.Address) types.Validator { - validatorRaw, exists := p.validators.Get(address.String()) - if !exists { - panic(types.ErrValidatorMissing) - } - - return *(validatorRaw.(*types.Validator)) -} - -func (p *PoC) GetValidators() []*types.Validator { - validators := make([]*types.Validator, 0, p.validators.Size()) - - p.validators.Iterate("", "", func(_ string, value interface{}) bool { - validator := value.(*types.Validator) - validators = append(validators, validator) - - return false - }) - - return validators -} diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types/types.gno index 937d01b6e6f..3586fcb2e42 100644 --- a/examples/gno.land/p/sys/vals/types/types.gno +++ b/examples/gno.land/p/sys/vals/types/types.gno @@ -1,6 +1,7 @@ package types import ( + "errors" "std" ) @@ -12,21 +13,21 @@ type ValsetProtocol interface { // TODO: This API is not ideal -- the address should be derived from // the public key, and not be passed in as such, but currently Gno // does not support crypto address derivation - AddValidator(address std.Address, pubKey string, power uint64) *Validator + AddValidator(address std.Address, pubKey string, power uint64) (*Validator, error) // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method should error out - RemoveValidator(address std.Address) *Validator + RemoveValidator(address std.Address) (*Validator, error) // IsValidator returns a flag indicating if the given // bech32 address is part of the validator set IsValidator(address std.Address) bool // GetValidator returns the validator using the given address - GetValidator(address std.Address) Validator + GetValidator(address std.Address) (*Validator, error) // GetValidators returns the currently active validator set - GetValidators() []*Validator + GetValidators() []Validator } // Validator represents a single chain validator @@ -41,13 +42,10 @@ const ( ValidatorRemovedEvent = "ValidatorRemoved" // emitted when a validator was removed from the set ) -const ( - // ErrCallerNotUserAccount is returned when the caller is not a user account - ErrCallerNotUserAccount = "caller not user account" - +var ( // ErrValidatorExists is returned when the validator is already in the set - ErrValidatorExists = "validator already exists" + ErrValidatorExists = errors.New("validator already exists") // ErrValidatorMissing is returned when the validator is not in the set - ErrValidatorMissing = "validator doesn't exist" + ErrValidatorMissing = errors.New("validator doesn't exist") ) diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index 221b2e3370c..d5e5e2eba35 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -2,6 +2,6 @@ module gno.land/r/sys/vals require ( gno.land/p/gov/proposal v0.0.0-latest - gno.land/p/sys/vals/poc v0.0.0-latest + gno.land/p/sys/vals/poa v0.0.0-latest gno.land/p/sys/vals/types v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/init.gno b/examples/gno.land/r/sys/vals/init.gno index 5da5ad3d736..f9451c62fdc 100644 --- a/examples/gno.land/r/sys/vals/init.gno +++ b/examples/gno.land/r/sys/vals/init.gno @@ -1,13 +1,13 @@ package vals import ( - "gno.land/p/sys/vals/poc" + "gno.land/p/sys/vals/poa" "gno.land/p/sys/vals/types" ) func init() { v = &vals{ - p: poc.NewPoC(), + p: poa.NewPoA(), changes: make([]types.Validator, 0), } } diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno index fafd539ddea..03933aa0317 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -61,6 +61,6 @@ func IsValidator(address string) bool { } // GetValidators returns the typed validator set -func GetValidators() []*types.Validator { +func GetValidators() []types.Validator { return v.p.GetValidators() } diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index 72ec5bad5f5..a22c73a7244 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -19,11 +19,12 @@ var v *vals // addValidator adds a new validator to the validator set. // If the validator is already present, the method errors out func addValidator(validator types.Validator) { - if val := v.p.AddValidator( - validator.Address, - validator.PubKey, - validator.VotingPower, - ); val != nil { + val, err := v.p.AddValidator(validator.Address, validator.PubKey, validator.VotingPower) + if err != nil { + panic(err) + } + + if val != nil { // Validator added, note the change v.changes = append(v.changes, *val) @@ -35,7 +36,12 @@ func addValidator(validator types.Validator) { // removeValidator removes the given validator from the set. // If the validator is not present in the set, the method errors out func removeValidator(address std.Address) { - if val := v.p.RemoveValidator(address); val != nil { + val, err := v.p.RemoveValidator(address) + if err != nil { + panic(err) + } + + if val != nil { // Validator removed, note the change v.changes = append(v.changes, types.Validator{ Address: val.Address, @@ -69,5 +75,6 @@ func Render(_ string) string { for _, change := range v.changes { output += "- " + string(change.Address) + " (" + strconv.FormatUint(change.VotingPower, 10) + ")\n" } + return output } diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index a5ae7be05ad..9012b89d512 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -53,6 +53,32 @@ func extractPanicErr(r interface{}) string { return "unknown error" } +// NoError asserts no error occurred +func NoError(t *T, err error) bool { + if err == nil { + return true + } + + t.Fatalf("Function errored with err %s", err) + + return false +} + +// ErrorIs asserts the given error matches the target error +func ErrorIs(t *T, err error, target error) bool { + if err == nil || target == nil { + return err == target + } + + if err.Error() != target.Error() { + t.Fatalf("Target error mismatch, expected %s, got %s", target, err) + + return false + } + + return true +} + // PanicsWithError asserts that the code inside the specified func panics, // and that the recovered panic value is an error that satisfies the given message func PanicsWithError(t *T, errString string, f func()) bool { From 537185249c565daa6aaf3b0aae4c2f527ac4f792 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 1 Jul 2024 19:23:48 +0200 Subject: [PATCH 45/71] Apply renames --- .../gno.land/p/{sys/vals => nt}/poa/gno.mod | 4 ++-- .../p/{sys/vals => nt}/poa/option.gno | 4 ++-- .../gno.land/p/{sys/vals => nt}/poa/poa.gno | 24 +++++++++---------- .../p/{sys/vals => nt}/poa/poa_test.gno | 18 +++++++------- examples/gno.land/p/sys/vals/gno.mod | 1 + .../gno.land/p/sys/vals/{types => }/types.gno | 2 +- examples/gno.land/p/sys/vals/types/gno.mod | 1 - .../gno.land/r/gov/dao/prop1_filetest.gno | 6 ++--- examples/gno.land/r/sys/vals/gno.mod | 3 +-- examples/gno.land/r/sys/vals/init.gno | 8 +++---- examples/gno.land/r/sys/vals/poc.gno | 6 ++--- examples/gno.land/r/sys/vals/vals.gno | 24 +++++++++---------- 12 files changed, 50 insertions(+), 51 deletions(-) rename examples/gno.land/p/{sys/vals => nt}/poa/gno.mod (63%) rename examples/gno.land/p/{sys/vals => nt}/poa/option.gno (70%) rename examples/gno.land/p/{sys/vals => nt}/poa/poa.gno (75%) rename examples/gno.land/p/{sys/vals => nt}/poa/poa_test.gno (93%) create mode 100644 examples/gno.land/p/sys/vals/gno.mod rename examples/gno.land/p/sys/vals/{types => }/types.gno (99%) delete mode 100644 examples/gno.land/p/sys/vals/types/gno.mod diff --git a/examples/gno.land/p/sys/vals/poa/gno.mod b/examples/gno.land/p/nt/poa/gno.mod similarity index 63% rename from examples/gno.land/p/sys/vals/poa/gno.mod rename to examples/gno.land/p/nt/poa/gno.mod index cec27e50c3c..91cc7d38703 100644 --- a/examples/gno.land/p/sys/vals/poa/gno.mod +++ b/examples/gno.land/p/nt/poa/gno.mod @@ -1,8 +1,8 @@ -module gno.land/p/sys/vals/poa +module gno.land/p/nt/poa require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest + gno.land/p/sys/vals v0.0.0-latest ) diff --git a/examples/gno.land/p/sys/vals/poa/option.gno b/examples/gno.land/p/nt/poa/option.gno similarity index 70% rename from examples/gno.land/p/sys/vals/poa/option.gno rename to examples/gno.land/p/nt/poa/option.gno index 900eee5a77e..7165b5d69e6 100644 --- a/examples/gno.land/p/sys/vals/poa/option.gno +++ b/examples/gno.land/p/nt/poa/option.gno @@ -1,11 +1,11 @@ package poa -import "gno.land/p/sys/vals/types" +import "gno.land/p/sys/vals" type Option func(*PoA) // WithInitialSet sets the initial PoA validator set -func WithInitialSet(validators []types.Validator) Option { +func WithInitialSet(validators []vals.Validator) Option { return func(p *PoA) { for _, validator := range validators { p.validators.Set(validator.Address.String(), validator) diff --git a/examples/gno.land/p/sys/vals/poa/poa.gno b/examples/gno.land/p/nt/poa/poa.gno similarity index 75% rename from examples/gno.land/p/sys/vals/poa/poa.gno rename to examples/gno.land/p/nt/poa/poa.gno index 44362e2bec1..ca4356261ad 100644 --- a/examples/gno.land/p/sys/vals/poa/poa.gno +++ b/examples/gno.land/p/nt/poa/poa.gno @@ -5,7 +5,7 @@ import ( "std" "gno.land/p/demo/avl" - "gno.land/p/sys/vals/types" + "gno.land/p/sys/vals" ) var ErrInvalidVotingPower = errors.New("invalid voting power") @@ -19,7 +19,7 @@ var ErrInvalidVotingPower = errors.New("invalid voting power") // To remove: // - proposed validator must be part of the set already type PoA struct { - validators *avl.Tree // std.Address -> types.Validator + validators *avl.Tree // std.Address -> vals.Validator } // NewPoA creates a new empty Proof of Authority validator set @@ -37,11 +37,11 @@ func NewPoA(opts ...Option) *PoA { return p } -func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*types.Validator, error) { +func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*vals.Validator, error) { // Validate that the operation is a valid call. // Check if the validator is already in the set if p.IsValidator(address) { - return nil, types.ErrValidatorExists + return nil, vals.ErrValidatorExists } // Make sure the voting power > 0 @@ -49,7 +49,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*t return nil, ErrInvalidVotingPower } - v := types.Validator{ + v := vals.Validator{ Address: address, PubKey: pubKey, // TODO: in the future, verify the public key VotingPower: power, @@ -61,7 +61,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*t return &v, nil } -func (p *PoA) RemoveValidator(address std.Address) (*types.Validator, error) { +func (p *PoA) RemoveValidator(address std.Address) (*vals.Validator, error) { // Validate that the operation is a valid call // Fetch the validator validator, err := p.GetValidator(address) @@ -81,22 +81,22 @@ func (p *PoA) IsValidator(address std.Address) bool { return exists } -func (p *PoA) GetValidator(address std.Address) (*types.Validator, error) { +func (p *PoA) GetValidator(address std.Address) (*vals.Validator, error) { validatorRaw, exists := p.validators.Get(address.String()) if !exists { - return nil, types.ErrValidatorMissing + return nil, vals.ErrValidatorMissing } - validator := validatorRaw.(types.Validator) + validator := validatorRaw.(vals.Validator) return &validator, nil } -func (p *PoA) GetValidators() []types.Validator { - validators := make([]types.Validator, 0, p.validators.Size()) +func (p *PoA) GetValidators() []vals.Validator { + validators := make([]vals.Validator, 0, p.validators.Size()) p.validators.Iterate("", "", func(_ string, value interface{}) bool { - validator := value.(types.Validator) + validator := value.(vals.Validator) validators = append(validators, validator) return false diff --git a/examples/gno.land/p/sys/vals/poa/poa_test.gno b/examples/gno.land/p/nt/poa/poa_test.gno similarity index 93% rename from examples/gno.land/p/sys/vals/poa/poa_test.gno rename to examples/gno.land/p/nt/poa/poa_test.gno index 5c9430cd3f9..62d7f2c9f0b 100644 --- a/examples/gno.land/p/sys/vals/poa/poa_test.gno +++ b/examples/gno.land/p/nt/poa/poa_test.gno @@ -4,26 +4,26 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/sys/vals" "gno.land/p/demo/ufmt" - "gno.land/p/sys/vals/types" ) // generateTestValidators generates a dummy validator set -func generateTestValidators(count int) []types.Validator { - vals := make([]types.Validator, 0, count) +func generateTestValidators(count int) []vals.Validator { + validators := make([]vals.Validator, 0, count) for i := 0; i < count; i++ { - val := types.Validator{ + val := vals.Validator{ Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), PubKey: "public-key", VotingPower: 1, } - vals = append(vals, val) + validators = append(validators, val) } - return vals + return validators } func TestPoA_AddValidator_Invalid(t *testing.T) { @@ -47,7 +47,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { // Attempt to add the validator _, err := p.AddValidator(proposalAddress, proposalKey, 1) - testing.ErrorIs(t, err, types.ErrValidatorExists) + testing.ErrorIs(t, err, vals.ErrValidatorExists) }) t.Run("invalid voting power", func(t *testing.T) { @@ -106,7 +106,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { // Attempt to remove the validator _, err := p.RemoveValidator(testutils.TestAddress("totally random")) - testing.ErrorIs(t, err, types.ErrValidatorMissing) + testing.ErrorIs(t, err, vals.ErrValidatorMissing) }) } @@ -144,7 +144,7 @@ func TestPoA_GetValidator(t *testing.T) { // Attempt to get the voting power _, err := p.GetValidator(testutils.TestAddress("caller")) - testing.ErrorIs(t, err, types.ErrValidatorMissing) + testing.ErrorIs(t, err, vals.ErrValidatorMissing) }) t.Run("validator fetched", func(t *testing.T) { diff --git a/examples/gno.land/p/sys/vals/gno.mod b/examples/gno.land/p/sys/vals/gno.mod new file mode 100644 index 00000000000..af5fa7dcd85 --- /dev/null +++ b/examples/gno.land/p/sys/vals/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sys/vals diff --git a/examples/gno.land/p/sys/vals/types/types.gno b/examples/gno.land/p/sys/vals/types.gno similarity index 99% rename from examples/gno.land/p/sys/vals/types/types.gno rename to examples/gno.land/p/sys/vals/types.gno index 3586fcb2e42..c8b6ce444e7 100644 --- a/examples/gno.land/p/sys/vals/types/types.gno +++ b/examples/gno.land/p/sys/vals/types.gno @@ -1,4 +1,4 @@ -package types +package vals import ( "errors" diff --git a/examples/gno.land/p/sys/vals/types/gno.mod b/examples/gno.land/p/sys/vals/types/gno.mod deleted file mode 100644 index f728025168c..00000000000 --- a/examples/gno.land/p/sys/vals/types/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/sys/vals/types diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index 17472169dd0..f2193896f26 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -10,14 +10,14 @@ package main import ( "std" - "gno.land/p/sys/vals/types" + pVals "gno.land/p/sys/vals" govdao "gno.land/r/gov/dao" "gno.land/r/sys/vals" ) func init() { - changesFn := func() []types.Validator { - return []types.Validator{ + changesFn := func() []pVals.Validator { + return []pVals.Validator{ { Address: std.Address("g12345678"), PubKey: "pubkey", diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index d5e5e2eba35..f7db291e4de 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -2,6 +2,5 @@ module gno.land/r/sys/vals require ( gno.land/p/gov/proposal v0.0.0-latest - gno.land/p/sys/vals/poa v0.0.0-latest - gno.land/p/sys/vals/types v0.0.0-latest + gno.land/p/nt/poa v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/init.gno b/examples/gno.land/r/sys/vals/init.gno index f9451c62fdc..aa0a47ef379 100644 --- a/examples/gno.land/r/sys/vals/init.gno +++ b/examples/gno.land/r/sys/vals/init.gno @@ -1,13 +1,13 @@ package vals import ( - "gno.land/p/sys/vals/poa" - "gno.land/p/sys/vals/types" + "gno.land/p/nt/poa" + "gno.land/p/sys/vals" ) func init() { - v = &vals{ + v = &valset{ p: poa.NewPoA(), - changes: make([]types.Validator, 0), + changes: make([]vals.Validator, 0), } } diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/vals/poc.gno index 03933aa0317..afc510a2c61 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/vals/poc.gno @@ -4,7 +4,7 @@ import ( "std" "gno.land/p/gov/proposal" - "gno.land/p/sys/vals/types" + "gno.land/p/sys/vals" ) const daoPkgPath = "gno.land/r/gov/dao" @@ -20,7 +20,7 @@ const ( // // Concept adapted from: // https://github.com/gnolang/gno/pull/1945 -func NewPropExecutor(changesFn func() []types.Validator) proposal.Executor { +func NewPropExecutor(changesFn func() []vals.Validator) proposal.Executor { if changesFn == nil { panic(errNoChangesProposed) } @@ -61,6 +61,6 @@ func IsValidator(address string) bool { } // GetValidators returns the typed validator set -func GetValidators() []types.Validator { +func GetValidators() []vals.Validator { return v.p.GetValidators() } diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/vals/vals.gno index a22c73a7244..04f0029dafa 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/vals/vals.gno @@ -4,21 +4,21 @@ import ( "std" "strconv" - "gno.land/p/sys/vals/types" + "gno.land/p/sys/vals" ) -// vals is the wrapper for the validator set protocol -type vals struct { - p types.ValsetProtocol // p is the underlying validator set protocol - changes []types.Validator // changes are the set changes that happened between scrapes +// valset is the wrapper for the validator set protocol +type valset struct { + p vals.ValsetProtocol // p is the underlying validator set protocol + changes []vals.Validator // changes are the set changes that happened between scrapes } // v holds the active on-chain validator set state -var v *vals +var v *valset // addValidator adds a new validator to the validator set. // If the validator is already present, the method errors out -func addValidator(validator types.Validator) { +func addValidator(validator vals.Validator) { val, err := v.p.AddValidator(validator.Address, validator.PubKey, validator.VotingPower) if err != nil { panic(err) @@ -29,7 +29,7 @@ func addValidator(validator types.Validator) { v.changes = append(v.changes, *val) // Emit the validator set change - std.Emit(types.ValidatorAddedEvent) + std.Emit(vals.ValidatorAddedEvent) } } @@ -43,21 +43,21 @@ func removeValidator(address std.Address) { if val != nil { // Validator removed, note the change - v.changes = append(v.changes, types.Validator{ + v.changes = append(v.changes, vals.Validator{ Address: val.Address, PubKey: val.PubKey, VotingPower: 0, // nullified the voting power indicates removal }) // Emit the validator set change - std.Emit(types.ValidatorRemovedEvent) + std.Emit(vals.ValidatorRemovedEvent) } } // getChanges returns the validator changes stored on the realm -func getChanges() []types.Validator { +func getChanges() []vals.Validator { // Construct the changes - changes := make([]types.Validator, len(v.changes)) + changes := make([]vals.Validator, len(v.changes)) copy(changes, v.changes) // Reset the changes set From e45814b24d1efd89a8f4687c7ce4526f7fe2d87f Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 1 Jul 2024 19:26:52 +0200 Subject: [PATCH 46/71] Tidy mods --- examples/gno.land/r/sys/vals/gno.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/vals/gno.mod index f7db291e4de..5deb036abf1 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/vals/gno.mod @@ -3,4 +3,5 @@ module gno.land/r/sys/vals require ( gno.land/p/gov/proposal v0.0.0-latest gno.land/p/nt/poa v0.0.0-latest + gno.land/p/sys/vals v0.0.0-latest ) From 96e136453af8243195e1be50b64a5428e5b9209e Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Mon, 1 Jul 2024 20:33:00 +0200 Subject: [PATCH 47/71] Rename vals -> validators --- examples/gno.land/p/nt/poa/gno.mod | 2 +- examples/gno.land/p/nt/poa/option.gno | 4 +-- examples/gno.land/p/nt/poa/poa.gno | 28 +++++++++---------- examples/gno.land/p/nt/poa/poa_test.gno | 18 ++++++------ examples/gno.land/p/sys/validators/gno.mod | 1 + .../p/sys/{vals => validators}/types.gno | 2 +- examples/gno.land/p/sys/vals/gno.mod | 1 - .../gno.land/r/gov/dao/prop1_filetest.gno | 10 +++---- examples/gno.land/r/sys/validators/doc.gno | 3 ++ .../r/sys/{vals => validators}/gno.mod | 4 +-- .../r/sys/{vals => validators}/init.gno | 6 ++-- .../r/sys/{vals => validators}/poc.gno | 8 +++--- .../vals.gno => validators/validators.gno} | 20 ++++++------- examples/gno.land/r/sys/vals/doc.gno | 3 -- 14 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 examples/gno.land/p/sys/validators/gno.mod rename examples/gno.land/p/sys/{vals => validators}/types.gno (98%) delete mode 100644 examples/gno.land/p/sys/vals/gno.mod create mode 100644 examples/gno.land/r/sys/validators/doc.gno rename examples/gno.land/r/sys/{vals => validators}/gno.mod (53%) rename examples/gno.land/r/sys/{vals => validators}/init.gno (50%) rename examples/gno.land/r/sys/{vals => validators}/poc.gno (88%) rename examples/gno.land/r/sys/{vals/vals.gno => validators/validators.gno} (74%) delete mode 100644 examples/gno.land/r/sys/vals/doc.gno diff --git a/examples/gno.land/p/nt/poa/gno.mod b/examples/gno.land/p/nt/poa/gno.mod index 91cc7d38703..9e70e1cd5d7 100644 --- a/examples/gno.land/p/nt/poa/gno.mod +++ b/examples/gno.land/p/nt/poa/gno.mod @@ -4,5 +4,5 @@ require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/sys/vals v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest ) diff --git a/examples/gno.land/p/nt/poa/option.gno b/examples/gno.land/p/nt/poa/option.gno index 7165b5d69e6..051ab2611f1 100644 --- a/examples/gno.land/p/nt/poa/option.gno +++ b/examples/gno.land/p/nt/poa/option.gno @@ -1,11 +1,11 @@ package poa -import "gno.land/p/sys/vals" +import "gno.land/p/sys/validators" type Option func(*PoA) // WithInitialSet sets the initial PoA validator set -func WithInitialSet(validators []vals.Validator) Option { +func WithInitialSet(validators []validators.Validator) Option { return func(p *PoA) { for _, validator := range validators { p.validators.Set(validator.Address.String(), validator) diff --git a/examples/gno.land/p/nt/poa/poa.gno b/examples/gno.land/p/nt/poa/poa.gno index ca4356261ad..3e495fc3be3 100644 --- a/examples/gno.land/p/nt/poa/poa.gno +++ b/examples/gno.land/p/nt/poa/poa.gno @@ -5,7 +5,7 @@ import ( "std" "gno.land/p/demo/avl" - "gno.land/p/sys/vals" + "gno.land/p/sys/validators" ) var ErrInvalidVotingPower = errors.New("invalid voting power") @@ -19,7 +19,7 @@ var ErrInvalidVotingPower = errors.New("invalid voting power") // To remove: // - proposed validator must be part of the set already type PoA struct { - validators *avl.Tree // std.Address -> vals.Validator + validators *avl.Tree // std.Address -> validators.Validator } // NewPoA creates a new empty Proof of Authority validator set @@ -37,11 +37,11 @@ func NewPoA(opts ...Option) *PoA { return p } -func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*vals.Validator, error) { +func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*validators.Validator, error) { // Validate that the operation is a valid call. // Check if the validator is already in the set if p.IsValidator(address) { - return nil, vals.ErrValidatorExists + return nil, validators.ErrValidatorExists } // Make sure the voting power > 0 @@ -49,7 +49,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*v return nil, ErrInvalidVotingPower } - v := vals.Validator{ + v := validators.Validator{ Address: address, PubKey: pubKey, // TODO: in the future, verify the public key VotingPower: power, @@ -61,7 +61,7 @@ func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*v return &v, nil } -func (p *PoA) RemoveValidator(address std.Address) (*vals.Validator, error) { +func (p *PoA) RemoveValidator(address std.Address) (*validators.Validator, error) { // Validate that the operation is a valid call // Fetch the validator validator, err := p.GetValidator(address) @@ -81,26 +81,26 @@ func (p *PoA) IsValidator(address std.Address) bool { return exists } -func (p *PoA) GetValidator(address std.Address) (*vals.Validator, error) { +func (p *PoA) GetValidator(address std.Address) (*validators.Validator, error) { validatorRaw, exists := p.validators.Get(address.String()) if !exists { - return nil, vals.ErrValidatorMissing + return nil, validators.ErrValidatorMissing } - validator := validatorRaw.(vals.Validator) + validator := validatorRaw.(validators.Validator) return &validator, nil } -func (p *PoA) GetValidators() []vals.Validator { - validators := make([]vals.Validator, 0, p.validators.Size()) +func (p *PoA) GetValidators() []validators.Validator { + vals := make([]validators.Validator, 0, p.validators.Size()) p.validators.Iterate("", "", func(_ string, value interface{}) bool { - validator := value.(vals.Validator) - validators = append(validators, validator) + validator := value.(validators.Validator) + vals = append(vals, validator) return false }) - return validators + return vals } diff --git a/examples/gno.land/p/nt/poa/poa_test.gno b/examples/gno.land/p/nt/poa/poa_test.gno index 62d7f2c9f0b..b9e23538e69 100644 --- a/examples/gno.land/p/nt/poa/poa_test.gno +++ b/examples/gno.land/p/nt/poa/poa_test.gno @@ -4,26 +4,26 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/p/sys/vals" + "gno.land/p/sys/validators" "gno.land/p/demo/ufmt" ) // generateTestValidators generates a dummy validator set -func generateTestValidators(count int) []vals.Validator { - validators := make([]vals.Validator, 0, count) +func generateTestValidators(count int) []validators.Validator { + vals := make([]validators.Validator, 0, count) for i := 0; i < count; i++ { - val := vals.Validator{ + val := validators.Validator{ Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), PubKey: "public-key", VotingPower: 1, } - validators = append(validators, val) + vals = append(vals, val) } - return validators + return vals } func TestPoA_AddValidator_Invalid(t *testing.T) { @@ -47,7 +47,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { // Attempt to add the validator _, err := p.AddValidator(proposalAddress, proposalKey, 1) - testing.ErrorIs(t, err, vals.ErrValidatorExists) + testing.ErrorIs(t, err, validators.ErrValidatorExists) }) t.Run("invalid voting power", func(t *testing.T) { @@ -106,7 +106,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { // Attempt to remove the validator _, err := p.RemoveValidator(testutils.TestAddress("totally random")) - testing.ErrorIs(t, err, vals.ErrValidatorMissing) + testing.ErrorIs(t, err, validators.ErrValidatorMissing) }) } @@ -144,7 +144,7 @@ func TestPoA_GetValidator(t *testing.T) { // Attempt to get the voting power _, err := p.GetValidator(testutils.TestAddress("caller")) - testing.ErrorIs(t, err, vals.ErrValidatorMissing) + testing.ErrorIs(t, err, validators.ErrValidatorMissing) }) t.Run("validator fetched", func(t *testing.T) { diff --git a/examples/gno.land/p/sys/validators/gno.mod b/examples/gno.land/p/sys/validators/gno.mod new file mode 100644 index 00000000000..9c7a38aada0 --- /dev/null +++ b/examples/gno.land/p/sys/validators/gno.mod @@ -0,0 +1 @@ +module gno.land/p/sys/validators diff --git a/examples/gno.land/p/sys/vals/types.gno b/examples/gno.land/p/sys/validators/types.gno similarity index 98% rename from examples/gno.land/p/sys/vals/types.gno rename to examples/gno.land/p/sys/validators/types.gno index c8b6ce444e7..3b033758ffb 100644 --- a/examples/gno.land/p/sys/vals/types.gno +++ b/examples/gno.land/p/sys/validators/types.gno @@ -1,4 +1,4 @@ -package vals +package validators import ( "errors" diff --git a/examples/gno.land/p/sys/vals/gno.mod b/examples/gno.land/p/sys/vals/gno.mod deleted file mode 100644 index af5fa7dcd85..00000000000 --- a/examples/gno.land/p/sys/vals/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/sys/vals diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index f2193896f26..1409a51960f 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -10,9 +10,9 @@ package main import ( "std" - pVals "gno.land/p/sys/vals" + pVals "gno.land/p/sys/validators" govdao "gno.land/r/gov/dao" - "gno.land/r/sys/vals" + "gno.land/r/sys/validators" ) func init() { @@ -38,7 +38,7 @@ func init() { // Wraps changesFn to emit a certified event only if executed from a // complete governance proposal process. - executor := vals.NewPropExecutor(changesFn) + executor := validators.NewPropExecutor(changesFn) // Create a proposal. // XXX: payment @@ -56,13 +56,13 @@ func main() { println("--") println(govdao.Render("1")) println("--") - println(vals.Render("")) + println(validators.Render("")) println("--") govdao.ExecuteProposal(1) println("--") println(govdao.Render("1")) println("--") - println(vals.Render("")) + println(validators.Render("")) } // Output: diff --git a/examples/gno.land/r/sys/validators/doc.gno b/examples/gno.land/r/sys/validators/doc.gno new file mode 100644 index 00000000000..d17b29ed110 --- /dev/null +++ b/examples/gno.land/r/sys/validators/doc.gno @@ -0,0 +1,3 @@ +// Package validators implements the on-chain validator set management through Proof of Contribution. +// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. +package validators diff --git a/examples/gno.land/r/sys/vals/gno.mod b/examples/gno.land/r/sys/validators/gno.mod similarity index 53% rename from examples/gno.land/r/sys/vals/gno.mod rename to examples/gno.land/r/sys/validators/gno.mod index 5deb036abf1..7025baa7a2a 100644 --- a/examples/gno.land/r/sys/vals/gno.mod +++ b/examples/gno.land/r/sys/validators/gno.mod @@ -1,7 +1,7 @@ -module gno.land/r/sys/vals +module gno.land/r/sys/validators require ( gno.land/p/gov/proposal v0.0.0-latest gno.land/p/nt/poa v0.0.0-latest - gno.land/p/sys/vals v0.0.0-latest + gno.land/p/sys/validators v0.0.0-latest ) diff --git a/examples/gno.land/r/sys/vals/init.gno b/examples/gno.land/r/sys/validators/init.gno similarity index 50% rename from examples/gno.land/r/sys/vals/init.gno rename to examples/gno.land/r/sys/validators/init.gno index aa0a47ef379..11b2aae9088 100644 --- a/examples/gno.land/r/sys/vals/init.gno +++ b/examples/gno.land/r/sys/validators/init.gno @@ -1,13 +1,13 @@ -package vals +package validators import ( "gno.land/p/nt/poa" - "gno.land/p/sys/vals" + "gno.land/p/sys/validators" ) func init() { v = &valset{ p: poa.NewPoA(), - changes: make([]vals.Validator, 0), + changes: make([]validators.Validator, 0), } } diff --git a/examples/gno.land/r/sys/vals/poc.gno b/examples/gno.land/r/sys/validators/poc.gno similarity index 88% rename from examples/gno.land/r/sys/vals/poc.gno rename to examples/gno.land/r/sys/validators/poc.gno index afc510a2c61..3c3d377716a 100644 --- a/examples/gno.land/r/sys/vals/poc.gno +++ b/examples/gno.land/r/sys/validators/poc.gno @@ -1,10 +1,10 @@ -package vals +package validators import ( "std" "gno.land/p/gov/proposal" - "gno.land/p/sys/vals" + "gno.land/p/sys/validators" ) const daoPkgPath = "gno.land/r/gov/dao" @@ -20,7 +20,7 @@ const ( // // Concept adapted from: // https://github.com/gnolang/gno/pull/1945 -func NewPropExecutor(changesFn func() []vals.Validator) proposal.Executor { +func NewPropExecutor(changesFn func() []validators.Validator) proposal.Executor { if changesFn == nil { panic(errNoChangesProposed) } @@ -61,6 +61,6 @@ func IsValidator(address string) bool { } // GetValidators returns the typed validator set -func GetValidators() []vals.Validator { +func GetValidators() []validators.Validator { return v.p.GetValidators() } diff --git a/examples/gno.land/r/sys/vals/vals.gno b/examples/gno.land/r/sys/validators/validators.gno similarity index 74% rename from examples/gno.land/r/sys/vals/vals.gno rename to examples/gno.land/r/sys/validators/validators.gno index 04f0029dafa..2b06dd27b6f 100644 --- a/examples/gno.land/r/sys/vals/vals.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -1,16 +1,16 @@ -package vals +package validators import ( "std" "strconv" - "gno.land/p/sys/vals" + "gno.land/p/sys/validators" ) // valset is the wrapper for the validator set protocol type valset struct { - p vals.ValsetProtocol // p is the underlying validator set protocol - changes []vals.Validator // changes are the set changes that happened between scrapes + p validators.ValsetProtocol // p is the underlying validator set protocol + changes []validators.Validator // changes are the set changes that happened between scrapes } // v holds the active on-chain validator set state @@ -18,7 +18,7 @@ var v *valset // addValidator adds a new validator to the validator set. // If the validator is already present, the method errors out -func addValidator(validator vals.Validator) { +func addValidator(validator validators.Validator) { val, err := v.p.AddValidator(validator.Address, validator.PubKey, validator.VotingPower) if err != nil { panic(err) @@ -29,7 +29,7 @@ func addValidator(validator vals.Validator) { v.changes = append(v.changes, *val) // Emit the validator set change - std.Emit(vals.ValidatorAddedEvent) + std.Emit(validators.ValidatorAddedEvent) } } @@ -43,21 +43,21 @@ func removeValidator(address std.Address) { if val != nil { // Validator removed, note the change - v.changes = append(v.changes, vals.Validator{ + v.changes = append(v.changes, validators.Validator{ Address: val.Address, PubKey: val.PubKey, VotingPower: 0, // nullified the voting power indicates removal }) // Emit the validator set change - std.Emit(vals.ValidatorRemovedEvent) + std.Emit(validators.ValidatorRemovedEvent) } } // getChanges returns the validator changes stored on the realm -func getChanges() []vals.Validator { +func getChanges() []validators.Validator { // Construct the changes - changes := make([]vals.Validator, len(v.changes)) + changes := make([]validators.Validator, len(v.changes)) copy(changes, v.changes) // Reset the changes set diff --git a/examples/gno.land/r/sys/vals/doc.gno b/examples/gno.land/r/sys/vals/doc.gno deleted file mode 100644 index 75405923376..00000000000 --- a/examples/gno.land/r/sys/vals/doc.gno +++ /dev/null @@ -1,3 +0,0 @@ -// Package vals implements the on-chain validator set management through Proof of Contribution. -// The Realm exposes only a public executor for govdao proposals, that can suggest validator set changes. -package vals From 5e9c3e0efa46ab4b9bfae9a4d8d2e2843c30cf39 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:44:14 +0200 Subject: [PATCH 48/71] Update examples/gno.land/r/gnoland/home/home.gno --- examples/gno.land/r/gnoland/home/home.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index 8d41f62c9d3..07ce461020b 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -182,7 +182,7 @@ func packageStaffPicks() ui.Element { ui.BulletList{ ui.Link{URL: "r/sys/names"}, ui.Link{URL: "r/sys/rewards"}, - ui.Link{URL: "r/sys/vals"}, + ui.Link{URL: "r/sys/validators"}, }, }, { ui.H4("[r/demo](https://github.com/gnolang/gno/tree/master/examples/gno.land/r/demo)"), From 0f2fb3932586b24b322cbc71661bb69e88625515 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Mon, 1 Jul 2024 21:09:49 +0200 Subject: [PATCH 49/71] Update examples/gno.land/r/gnoland/home/home_filetest.gno --- examples/gno.land/r/gnoland/home/home_filetest.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno index 4cb7664d14f..919f8dd4fbc 100644 --- a/examples/gno.land/r/gnoland/home/home_filetest.gno +++ b/examples/gno.land/r/gnoland/home/home_filetest.gno @@ -118,7 +118,7 @@ func main() { // // - [r/sys/names](r/sys/names) // - [r/sys/rewards](r/sys/rewards) -// - [r/sys/vals](r/sys/vals) +// - [r/sys/validators](r/sys/validators) // //
//
From da51c238ac41618fe85245fe14dee21af7c6f8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Tue, 2 Jul 2024 11:44:41 +0200 Subject: [PATCH 50/71] Update examples/gno.land/r/sys/validators/poc.gno Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- examples/gno.land/r/sys/validators/poc.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/r/sys/validators/poc.gno b/examples/gno.land/r/sys/validators/poc.gno index 3c3d377716a..1461eea1c8a 100644 --- a/examples/gno.land/r/sys/validators/poc.gno +++ b/examples/gno.land/r/sys/validators/poc.gno @@ -56,8 +56,8 @@ func assertGovDAOCaller() { // IsValidator returns a flag indicating if the given bech32 address // is part of the validator set -func IsValidator(address string) bool { - return v.p.IsValidator(std.Address(address)) +func IsValidator(addr std.Address) bool { + return v.p.IsValidator(addr) } // GetValidators returns the typed validator set From b25171dcf2cba38ec160cd23b17b02a7f1048711 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 2 Jul 2024 11:47:40 +0200 Subject: [PATCH 51/71] Move getChanges to gnosdk.gno --- examples/gno.land/r/sys/validators/gnosdk.gno | 16 ++++++++++++++++ .../gno.land/r/sys/validators/validators.gno | 12 ------------ 2 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 examples/gno.land/r/sys/validators/gnosdk.gno diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno new file mode 100644 index 00000000000..f557f5e1f4a --- /dev/null +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -0,0 +1,16 @@ +package validators + +import "gno.land/p/sys/validators" + +// getChanges returns the validator changes stored on the realm. +// This function is unexported and intended to be called by gno.land through the GnoSDK +func getChanges() []validators.Validator { + // Construct the changes + changes := make([]validators.Validator, len(v.changes)) + copy(changes, v.changes) + + // Reset the changes set + v.changes = v.changes[:0] + + return changes +} diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index 2b06dd27b6f..edd3ddf088f 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -54,18 +54,6 @@ func removeValidator(address std.Address) { } } -// getChanges returns the validator changes stored on the realm -func getChanges() []validators.Validator { - // Construct the changes - changes := make([]validators.Validator, len(v.changes)) - copy(changes, v.changes) - - // Reset the changes set - v.changes = v.changes[:0] - - return changes -} - func Render(_ string) string { if len(v.changes) == 0 { return "No valset changes to apply." From fa341a685730eb52fd7e3681d6f682afdf6c80f3 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 2 Jul 2024 11:53:23 +0200 Subject: [PATCH 52/71] Simplify valset to global package vars --- examples/gno.land/r/sys/validators/gnosdk.gno | 8 +++---- examples/gno.land/r/sys/validators/init.gno | 9 ++++---- examples/gno.land/r/sys/validators/poc.gno | 4 ++-- .../gno.land/r/sys/validators/validators.gno | 22 ++++++++----------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno index f557f5e1f4a..ae369726e47 100644 --- a/examples/gno.land/r/sys/validators/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -6,11 +6,11 @@ import "gno.land/p/sys/validators" // This function is unexported and intended to be called by gno.land through the GnoSDK func getChanges() []validators.Validator { // Construct the changes - changes := make([]validators.Validator, len(v.changes)) - copy(changes, v.changes) + valsetChanges := make([]validators.Validator, len(changes)) + copy(valsetChanges, changes) // Reset the changes set - v.changes = v.changes[:0] + changes = changes[:0] - return changes + return valsetChanges } diff --git a/examples/gno.land/r/sys/validators/init.gno b/examples/gno.land/r/sys/validators/init.gno index 11b2aae9088..72a0cda87cc 100644 --- a/examples/gno.land/r/sys/validators/init.gno +++ b/examples/gno.land/r/sys/validators/init.gno @@ -6,8 +6,9 @@ import ( ) func init() { - v = &valset{ - p: poa.NewPoA(), - changes: make([]validators.Validator, 0), - } + // The default valset protocol is PoA + vp = poa.NewPoA() + + // No changes to apply initially + changes = make([]validators.Validator, 0) } diff --git a/examples/gno.land/r/sys/validators/poc.gno b/examples/gno.land/r/sys/validators/poc.gno index 1461eea1c8a..e088b3b4293 100644 --- a/examples/gno.land/r/sys/validators/poc.gno +++ b/examples/gno.land/r/sys/validators/poc.gno @@ -57,10 +57,10 @@ func assertGovDAOCaller() { // IsValidator returns a flag indicating if the given bech32 address // is part of the validator set func IsValidator(addr std.Address) bool { - return v.p.IsValidator(addr) + return vp.IsValidator(addr) } // GetValidators returns the typed validator set func GetValidators() []validators.Validator { - return v.p.GetValidators() + return vp.GetValidators() } diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index edd3ddf088f..81e51b61bb0 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -7,26 +7,22 @@ import ( "gno.land/p/sys/validators" ) -// valset is the wrapper for the validator set protocol -type valset struct { - p validators.ValsetProtocol // p is the underlying validator set protocol +var ( + vp validators.ValsetProtocol // p is the underlying validator set protocol changes []validators.Validator // changes are the set changes that happened between scrapes -} - -// v holds the active on-chain validator set state -var v *valset +) // addValidator adds a new validator to the validator set. // If the validator is already present, the method errors out func addValidator(validator validators.Validator) { - val, err := v.p.AddValidator(validator.Address, validator.PubKey, validator.VotingPower) + val, err := vp.AddValidator(validator.Address, validator.PubKey, validator.VotingPower) if err != nil { panic(err) } if val != nil { // Validator added, note the change - v.changes = append(v.changes, *val) + changes = append(changes, *val) // Emit the validator set change std.Emit(validators.ValidatorAddedEvent) @@ -36,14 +32,14 @@ func addValidator(validator validators.Validator) { // removeValidator removes the given validator from the set. // If the validator is not present in the set, the method errors out func removeValidator(address std.Address) { - val, err := v.p.RemoveValidator(address) + val, err := vp.RemoveValidator(address) if err != nil { panic(err) } if val != nil { // Validator removed, note the change - v.changes = append(v.changes, validators.Validator{ + changes = append(changes, validators.Validator{ Address: val.Address, PubKey: val.PubKey, VotingPower: 0, // nullified the voting power indicates removal @@ -55,12 +51,12 @@ func removeValidator(address std.Address) { } func Render(_ string) string { - if len(v.changes) == 0 { + if len(changes) == 0 { return "No valset changes to apply." } output := "Valset changes to apply:\n" - for _, change := range v.changes { + for _, change := range changes { output += "- " + string(change.Address) + " (" + strconv.FormatUint(change.VotingPower, 10) + ")\n" } From a0fc2c1e6b83a2ce88a94f6ba5292b34eeeb568e Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 2 Jul 2024 15:06:57 +0200 Subject: [PATCH 53/71] Standardize return API for ValsetProtocol --- examples/gno.land/p/nt/poa/poa.gno | 18 ++++++------ examples/gno.land/p/sys/validators/types.gno | 6 ++-- .../gno.land/r/sys/validators/validators.gno | 28 ++++++++----------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/examples/gno.land/p/nt/poa/poa.gno b/examples/gno.land/p/nt/poa/poa.gno index 3e495fc3be3..1eab427f642 100644 --- a/examples/gno.land/p/nt/poa/poa.gno +++ b/examples/gno.land/p/nt/poa/poa.gno @@ -37,16 +37,16 @@ func NewPoA(opts ...Option) *PoA { return p } -func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*validators.Validator, error) { +func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (validators.Validator, error) { // Validate that the operation is a valid call. // Check if the validator is already in the set if p.IsValidator(address) { - return nil, validators.ErrValidatorExists + return validators.Validator{}, validators.ErrValidatorExists } // Make sure the voting power > 0 if power == 0 { - return nil, ErrInvalidVotingPower + return validators.Validator{}, ErrInvalidVotingPower } v := validators.Validator{ @@ -58,15 +58,15 @@ func (p *PoA) AddValidator(address std.Address, pubKey string, power uint64) (*v // Add the validator to the set p.validators.Set(address.String(), v) - return &v, nil + return v, nil } -func (p *PoA) RemoveValidator(address std.Address) (*validators.Validator, error) { +func (p *PoA) RemoveValidator(address std.Address) (validators.Validator, error) { // Validate that the operation is a valid call // Fetch the validator validator, err := p.GetValidator(address) if err != nil { - return nil, err + return validators.Validator{}, err } // Remove the validator from the set @@ -81,15 +81,15 @@ func (p *PoA) IsValidator(address std.Address) bool { return exists } -func (p *PoA) GetValidator(address std.Address) (*validators.Validator, error) { +func (p *PoA) GetValidator(address std.Address) (validators.Validator, error) { validatorRaw, exists := p.validators.Get(address.String()) if !exists { - return nil, validators.ErrValidatorMissing + return validators.Validator{}, validators.ErrValidatorMissing } validator := validatorRaw.(validators.Validator) - return &validator, nil + return validator, nil } func (p *PoA) GetValidators() []validators.Validator { diff --git a/examples/gno.land/p/sys/validators/types.gno b/examples/gno.land/p/sys/validators/types.gno index 3b033758ffb..bd7d5df2ba8 100644 --- a/examples/gno.land/p/sys/validators/types.gno +++ b/examples/gno.land/p/sys/validators/types.gno @@ -13,18 +13,18 @@ type ValsetProtocol interface { // TODO: This API is not ideal -- the address should be derived from // the public key, and not be passed in as such, but currently Gno // does not support crypto address derivation - AddValidator(address std.Address, pubKey string, power uint64) (*Validator, error) + AddValidator(address std.Address, pubKey string, power uint64) (Validator, error) // RemoveValidator removes the given validator from the set. // If the validator is not present in the set, the method should error out - RemoveValidator(address std.Address) (*Validator, error) + RemoveValidator(address std.Address) (Validator, error) // IsValidator returns a flag indicating if the given // bech32 address is part of the validator set IsValidator(address std.Address) bool // GetValidator returns the validator using the given address - GetValidator(address std.Address) (*Validator, error) + GetValidator(address std.Address) (Validator, error) // GetValidators returns the currently active validator set GetValidators() []Validator diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index 81e51b61bb0..c40b550e6dc 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -20,13 +20,11 @@ func addValidator(validator validators.Validator) { panic(err) } - if val != nil { - // Validator added, note the change - changes = append(changes, *val) + // Validator added, note the change + changes = append(changes, val) - // Emit the validator set change - std.Emit(validators.ValidatorAddedEvent) - } + // Emit the validator set change + std.Emit(validators.ValidatorAddedEvent) } // removeValidator removes the given validator from the set. @@ -37,17 +35,15 @@ func removeValidator(address std.Address) { panic(err) } - if val != nil { - // Validator removed, note the change - changes = append(changes, validators.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: 0, // nullified the voting power indicates removal - }) + // Validator removed, note the change + changes = append(changes, validators.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: 0, // nullified the voting power indicates removal + }) - // Emit the validator set change - std.Emit(validators.ValidatorRemovedEvent) - } + // Emit the validator set change + std.Emit(validators.ValidatorRemovedEvent) } func Render(_ string) string { From cc6bc6247255c23914a4231a69ce146d1deb16ee Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 2 Jul 2024 15:21:02 +0200 Subject: [PATCH 54/71] Merge master --- examples/gno.land/p/nt/poa/gno.mod | 2 + examples/gno.land/p/nt/poa/poa_test.gno | 24 ++++--- gnovm/stdlibs/testing/testing.gno | 87 ------------------------- 3 files changed, 13 insertions(+), 100 deletions(-) diff --git a/examples/gno.land/p/nt/poa/gno.mod b/examples/gno.land/p/nt/poa/gno.mod index 9e70e1cd5d7..5c1b75eb05a 100644 --- a/examples/gno.land/p/nt/poa/gno.mod +++ b/examples/gno.land/p/nt/poa/gno.mod @@ -3,6 +3,8 @@ module gno.land/p/nt/poa require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest + gno.land/p/demo/urequire v0.0.0-latest gno.land/p/sys/validators v0.0.0-latest ) diff --git a/examples/gno.land/p/nt/poa/poa_test.gno b/examples/gno.land/p/nt/poa/poa_test.gno index b9e23538e69..dfec387774c 100644 --- a/examples/gno.land/p/nt/poa/poa_test.gno +++ b/examples/gno.land/p/nt/poa/poa_test.gno @@ -4,6 +4,8 @@ import ( "testing" "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" "gno.land/p/sys/validators" "gno.land/p/demo/ufmt" @@ -47,7 +49,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { // Attempt to add the validator _, err := p.AddValidator(proposalAddress, proposalKey, 1) - testing.ErrorIs(t, err, validators.ErrValidatorExists) + uassert.ErrorIs(t, err, validators.ErrValidatorExists) }) t.Run("invalid voting power", func(t *testing.T) { @@ -63,7 +65,7 @@ func TestPoA_AddValidator_Invalid(t *testing.T) { // Attempt to add the validator _, err := p.AddValidator(proposalAddress, proposalKey, 0) - testing.ErrorIs(t, err, ErrInvalidVotingPower) + uassert.ErrorIs(t, err, ErrInvalidVotingPower) }) } @@ -80,7 +82,7 @@ func TestPoA_AddValidator(t *testing.T) { // Attempt to add the validator _, err := p.AddValidator(proposalAddress, proposalKey, 1) - testing.NoError(t, err) + uassert.NoError(t, err) // Make sure the validator is added if !p.IsValidator(proposalAddress) || p.validators.Size() != 1 { @@ -106,7 +108,7 @@ func TestPoA_RemoveValidator_Invalid(t *testing.T) { // Attempt to remove the validator _, err := p.RemoveValidator(testutils.TestAddress("totally random")) - testing.ErrorIs(t, err, validators.ErrValidatorMissing) + uassert.ErrorIs(t, err, validators.ErrValidatorMissing) }) } @@ -125,7 +127,7 @@ func TestPoA_RemoveValidator(t *testing.T) { // Attempt to remove the validator _, err := p.RemoveValidator(proposalAddress) - testing.NoError(t, err) + urequire.NoError(t, err) // Make sure the validator is removed if p.IsValidator(proposalAddress) || p.validators.Size() != 0 { @@ -144,7 +146,7 @@ func TestPoA_GetValidator(t *testing.T) { // Attempt to get the voting power _, err := p.GetValidator(testutils.TestAddress("caller")) - testing.ErrorIs(t, err, validators.ErrValidatorMissing) + uassert.ErrorIs(t, err, validators.ErrValidatorMissing) }) t.Run("validator fetched", func(t *testing.T) { @@ -167,7 +169,7 @@ func TestPoA_GetValidator(t *testing.T) { // Get the validator val, err := p.GetValidator(address) - testing.NoError(t, err) + urequire.NoError(t, err) // Validate the address if val.Address != address { @@ -225,14 +227,10 @@ func TestPoA_GetValidators(t *testing.T) { } // Validate the voting power - if val.VotingPower != initialVal.VotingPower { - t.Fatal("invalid voting power") - } + uassert.Equal(t, val.VotingPower, initialVal.VotingPower) // Validate the public key - if val.PubKey != initialVal.PubKey { - t.Fatal("invalid public key") - } + uassert.Equal(t, val.PubKey, initialVal.PubKey) } } }) diff --git a/gnovm/stdlibs/testing/testing.gno b/gnovm/stdlibs/testing/testing.gno index 9012b89d512..6e55c5cc283 100644 --- a/gnovm/stdlibs/testing/testing.gno +++ b/gnovm/stdlibs/testing/testing.gno @@ -39,93 +39,6 @@ func Recover(result Setter) { panic(r) } -func extractPanicErr(r interface{}) string { - err, ok := r.(error) - if ok { - return err.Error() - } - - errStr, ok := r.(string) - if ok { - return errStr - } - - return "unknown error" -} - -// NoError asserts no error occurred -func NoError(t *T, err error) bool { - if err == nil { - return true - } - - t.Fatalf("Function errored with err %s", err) - - return false -} - -// ErrorIs asserts the given error matches the target error -func ErrorIs(t *T, err error, target error) bool { - if err == nil || target == nil { - return err == target - } - - if err.Error() != target.Error() { - t.Fatalf("Target error mismatch, expected %s, got %s", target, err) - - return false - } - - return true -} - -// PanicsWithError asserts that the code inside the specified func panics, -// and that the recovered panic value is an error that satisfies the given message -func PanicsWithError(t *T, errString string, f func()) bool { - t.Helper() - - var valid bool - - defer func() { - if r := recover(); r != nil { - // Check if the error matches - panicErr := extractPanicErr(r) - if panicErr == errString { - valid = true - - return - } - - t.Fatalf("Function panicked with err, %s", panicErr) - } - }() - - // Run the callback - f() - - return valid -} - -// NotPanics asserts that the code inside the specified func does NOT panic -func NotPanics(t *T, f func()) bool { - t.Helper() - - valid := true - - defer func() { - if r := recover(); r != nil { - valid = false - - t.Fatalf("Function panicked with err, %s", extractPanicErr(r)) - } - }() - - // Run the callback - f() - - return valid -} - type Setter interface { Set(v interface{}) } From d2c4b6c5b19e927d33c3b4a42cd90c548614fbe5 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Tue, 2 Jul 2024 21:07:15 +0200 Subject: [PATCH 55/71] Add unit tests for EndBlocker --- gno.land/pkg/gnoland/app_test.go | 263 ++++++++++++++++++++++++++++++ gno.land/pkg/gnoland/mock_test.go | 79 +++++++++ gno.land/pkg/gnoland/vals.go | 2 +- 3 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 gno.land/pkg/gnoland/app_test.go create mode 100644 gno.land/pkg/gnoland/mock_test.go diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go new file mode 100644 index 00000000000..e29d7675af5 --- /dev/null +++ b/gno.land/pkg/gnoland/app_test.go @@ -0,0 +1,263 @@ +package gnoland + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/gnovm/stdlibs/std" + abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" + "github.com/gnolang/gno/tm2/pkg/bft/types" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// generateValidatorUpdates generates dummy validator updates +func generateValidatorUpdates(t *testing.T, count int) []abci.ValidatorUpdate { + t.Helper() + + validators := make([]abci.ValidatorUpdate, 0, count) + + for i := 0; i < count; i++ { + // Generate a random private key + key := getDummyKey(t) + + validator := abci.ValidatorUpdate{ + Address: key.Address(), + PubKey: key, + Power: 1, + } + + validators = append(validators, validator) + } + + return validators +} + +func TestEndBlocker(t *testing.T) { + t.Parallel() + + constructVMResponse := func(updates []abci.ValidatorUpdate) string { + var builder strings.Builder + + builder.WriteString("(slice[") + + for i, update := range updates { + builder.WriteString( + fmt.Sprintf( + "(struct{(%q std.Address),(%q string),(%d uint64)} gno.land/p/sys/validators.Validator)", + update.Address, + update.PubKey, + update.Power, + ), + ) + + if i < len(updates)-1 { + builder.WriteString(",") + } + } + + builder.WriteString("] []gno.land/p/sys/validators.Validator)") + + return builder.String() + } + + newCommonEvSwitch := func() *mockEventSwitch { + var cb events.EventCallback + + return &mockEventSwitch{ + addListenerFn: func(_ string, callback events.EventCallback) { + cb = callback + }, + fireEventFn: func(event events.Event) { + cb(event) + }, + } + } + + t.Run("no collector events", func(t *testing.T) { + t.Parallel() + + noFilter := func(e events.Event) []validatorUpdate { + return []validatorUpdate{} + } + + // Create the collector + c := newCollector[validatorUpdate](&mockEventSwitch{}, noFilter) + + // Create the EndBlocker + eb := EndBlocker(c, nil, log.NewNoopLogger()) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was empty + assert.Equal(t, abci.ResponseEndBlock{}, res) + }) + + t.Run("invalid VM call", func(t *testing.T) { + t.Parallel() + + var ( + noFilter = func(e events.Event) []validatorUpdate { + return make([]validatorUpdate, 1) // 1 update + } + + vmCalled bool + + mockEventSwitch = newCommonEvSwitch() + + mockVMKeeper = &mockVMKeeper{ + callFn: func(_ sdk.Context, call vm.MsgCall) (string, error) { + vmCalled = true + + require.Equal(t, crypto.Address{}, call.Caller) + require.Equal(t, valRealm, call.PkgPath) + require.NotNil(t, call.Func) + + return "", errors.New("random call error") + }, + } + ) + + // Create the collector + c := newCollector[validatorUpdate](mockEventSwitch, noFilter) + + // Fire a GnoVM event + mockEventSwitch.FireEvent(std.GnoEvent{}) + + // Create the EndBlocker + eb := EndBlocker(c, mockVMKeeper, log.NewNoopLogger()) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was empty + assert.Equal(t, abci.ResponseEndBlock{}, res) + + // Make sure the VM was called + assert.True(t, vmCalled) + }) + + t.Run("empty VM response", func(t *testing.T) { + t.Parallel() + + var ( + noFilter = func(e events.Event) []validatorUpdate { + return make([]validatorUpdate, 1) // 1 update + } + + vmCalled bool + + mockEventSwitch = newCommonEvSwitch() + + mockVMKeeper = &mockVMKeeper{ + callFn: func(_ sdk.Context, call vm.MsgCall) (string, error) { + vmCalled = true + + require.Equal(t, crypto.Address{}, call.Caller) + require.Equal(t, valRealm, call.PkgPath) + require.NotNil(t, call.Func) + + return constructVMResponse([]abci.ValidatorUpdate{}), nil + }, + } + ) + + // Create the collector + c := newCollector[validatorUpdate](mockEventSwitch, noFilter) + + // Fire a GnoVM event + mockEventSwitch.FireEvent(std.GnoEvent{}) + + // Create the EndBlocker + eb := EndBlocker(c, mockVMKeeper, log.NewNoopLogger()) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was empty + assert.Equal(t, abci.ResponseEndBlock{}, res) + + // Make sure the VM was called + assert.True(t, vmCalled) + }) + + t.Run("multiple valset updates", func(t *testing.T) { + t.Parallel() + + var ( + changes = generateValidatorUpdates(t, 100) + + mockEventSwitch = newCommonEvSwitch() + + mockVMKeeper = &mockVMKeeper{ + callFn: func(_ sdk.Context, call vm.MsgCall) (string, error) { + require.Equal(t, crypto.Address{}, call.Caller) + require.Equal(t, valRealm, call.PkgPath) + require.NotNil(t, call.Func) + + return constructVMResponse(changes), nil + }, + } + ) + + // Create the collector + c := newCollector[validatorUpdate](mockEventSwitch, validatorEventFilter) + + // Construct the GnoVM events + vmEvents := make([]abci.Event, 0, len(changes)) + for index := range changes { + event := std.GnoEvent{ + Type: validatorAddedEvent, + PkgPath: valRealm, + } + + // Make half the changes validator removes + if index%2 == 0 { + changes[index].Power = 0 + + event = std.GnoEvent{ + Type: validatorRemovedEvent, + PkgPath: valRealm, + } + } + + vmEvents = append(vmEvents, event) + } + + // Fire the tx result event + txEvent := types.EventTx{ + Result: types.TxResult{ + Response: abci.ResponseDeliverTx{ + ResponseBase: abci.ResponseBase{ + Events: vmEvents, + }, + }, + }, + } + + mockEventSwitch.FireEvent(txEvent) + + // Create the EndBlocker + eb := EndBlocker(c, mockVMKeeper, log.NewNoopLogger()) + + // Run the EndBlocker + res := eb(sdk.Context{}, abci.RequestEndBlock{}) + + // Verify the response was not empty + require.Len(t, res.ValidatorUpdates, len(changes)) + + for index, update := range res.ValidatorUpdates { + assert.Equal(t, changes[index].Address, update.Address) + assert.True(t, changes[index].PubKey.Equals(update.PubKey)) + assert.Equal(t, changes[index].Power, update.Power) + } + }) +} diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go new file mode 100644 index 00000000000..214f766b43b --- /dev/null +++ b/gno.land/pkg/gnoland/mock_test.go @@ -0,0 +1,79 @@ +package gnoland + +import ( + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/service" +) + +type ( + fireEventDelegate func(events.Event) + addListenerDelegate func(string, events.EventCallback) + removeListenerDelegate func(string) +) + +type mockEventSwitch struct { + service.BaseService + + fireEventFn fireEventDelegate + addListenerFn addListenerDelegate + removeListenerFn removeListenerDelegate +} + +func (m *mockEventSwitch) FireEvent(ev events.Event) { + if m.fireEventFn != nil { + m.fireEventFn(ev) + } +} + +func (m *mockEventSwitch) AddListener( + listenerID string, + cb events.EventCallback, +) { + if m.addListenerFn != nil { + m.addListenerFn(listenerID, cb) + } +} + +func (m *mockEventSwitch) RemoveListener(listenerID string) { + if m.removeListenerFn != nil { + m.removeListenerFn(listenerID) + } +} + +type ( + addPackageDelegate func(sdk.Context, vm.MsgAddPackage) error + callDelegate func(sdk.Context, vm.MsgCall) (string, error) + runDelegate func(sdk.Context, vm.MsgRun) (string, error) +) + +type mockVMKeeper struct { + addPackageFn addPackageDelegate + callFn callDelegate + runFn runDelegate +} + +func (m *mockVMKeeper) AddPackage(ctx sdk.Context, msg vm.MsgAddPackage) error { + if m.addPackageFn != nil { + return m.addPackageFn(ctx, msg) + } + + return nil +} + +func (m *mockVMKeeper) Call(ctx sdk.Context, msg vm.MsgCall) (res string, err error) { + if m.callFn != nil { + return m.callFn(ctx, msg) + } + + return "", nil +} + +func (m *mockVMKeeper) Run(ctx sdk.Context, msg vm.MsgRun) (res string, err error) { + if m.runFn != nil { + return m.runFn(ctx, msg) + } + + return "", nil +} diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go index 831cd327fa3..1e9a4e69e6b 100644 --- a/gno.land/pkg/gnoland/vals.go +++ b/gno.land/pkg/gnoland/vals.go @@ -50,7 +50,7 @@ func validatorEventFilter(event events.Event) []validatorUpdate { case validatorAddedEvent, validatorRemovedEvent: // We don't pass data around with the events, but a single // notification is enough to "trigger" a VM scrape - return []validatorUpdate{} + return []validatorUpdate{{}} default: continue } From 0b3cf19108b55ce0fbcfdbcbae8a65eca3d0d064 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 3 Jul 2024 14:19:37 +0200 Subject: [PATCH 56/71] Add base event switch for in-memory node --- gno.land/pkg/gnoland/node_inmemory.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 1e6f929f7f9..1e779535b50 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -13,6 +13,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/crypto/ed25519" "github.com/gnolang/gno/tm2/pkg/db" "github.com/gnolang/gno/tm2/pkg/db/memdb" + "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/p2p" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -91,6 +92,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, GenesisTxHandler: cfg.GenesisTxHandler, MaxCycles: cfg.GenesisMaxVMCycles, DB: memdb.NewMemDB(), + EventSwitch: events.NewEventSwitch(), }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) From 40a8e03c43367b29d5f01497400c0d1c072892c9 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 3 Jul 2024 18:11:37 +0200 Subject: [PATCH 57/71] Change up the GetChanges API --- .../gno.land/r/gov/dao/prop1_filetest.gno | 8 ++--- examples/gno.land/r/sys/validators/gno.mod | 1 + examples/gno.land/r/sys/validators/gnosdk.gno | 20 +++++++---- examples/gno.land/r/sys/validators/init.gno | 3 +- .../gno.land/r/sys/validators/validators.gno | 35 ++++++++++++++----- gno.land/pkg/gnoland/app.go | 24 ++++++++++--- gno.land/pkg/gnoland/app_test.go | 9 +++-- gno.land/pkg/gnoland/mock_test.go | 29 +++++++++++++++ gno.land/pkg/gnoland/vals.go | 4 +-- 9 files changed, 100 insertions(+), 33 deletions(-) diff --git a/examples/gno.land/r/gov/dao/prop1_filetest.gno b/examples/gno.land/r/gov/dao/prop1_filetest.gno index a7fd1097cfc..cca7aa91ff2 100644 --- a/examples/gno.land/r/gov/dao/prop1_filetest.gno +++ b/examples/gno.land/r/gov/dao/prop1_filetest.gno @@ -91,7 +91,7 @@ func main() { // Status: succeeded // Author: g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm // -- -// Valset changes to apply: -// - g12345678 (10) -// - g000000000 (10) -// - g000000000 (0) +// Valset changes: +// - #123: g12345678 (10) +// - #123: g000000000 (10) +// - #123: g000000000 (0) diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/gno.mod index 7025baa7a2a..c865861dad5 100644 --- a/examples/gno.land/r/sys/validators/gno.mod +++ b/examples/gno.land/r/sys/validators/gno.mod @@ -1,6 +1,7 @@ module gno.land/r/sys/validators require ( + gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/gov/proposal v0.0.0-latest gno.land/p/nt/poa v0.0.0-latest gno.land/p/sys/validators v0.0.0-latest diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno index ae369726e47..f598c08bf8a 100644 --- a/examples/gno.land/r/sys/validators/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -1,16 +1,22 @@ package validators -import "gno.land/p/sys/validators" +import ( + "gno.land/p/sys/validators" +) -// getChanges returns the validator changes stored on the realm. +// GetChanges returns the validator changes stored on the realm, since the given block number. // This function is unexported and intended to be called by gno.land through the GnoSDK -func getChanges() []validators.Validator { +func GetChanges(from int64) []validators.Validator { // Construct the changes - valsetChanges := make([]validators.Validator, len(changes)) - copy(valsetChanges, changes) + valsetChanges := make([]validators.Validator, 0) - // Reset the changes set - changes = changes[:0] + for _, change := range changes { + if change.blockNum < from { + continue + } + + valsetChanges = append(valsetChanges, change.validator) + } return valsetChanges } diff --git a/examples/gno.land/r/sys/validators/init.gno b/examples/gno.land/r/sys/validators/init.gno index 72a0cda87cc..b7c7d1cce4b 100644 --- a/examples/gno.land/r/sys/validators/init.gno +++ b/examples/gno.land/r/sys/validators/init.gno @@ -2,7 +2,6 @@ package validators import ( "gno.land/p/nt/poa" - "gno.land/p/sys/validators" ) func init() { @@ -10,5 +9,5 @@ func init() { vp = poa.NewPoA() // No changes to apply initially - changes = make([]validators.Validator, 0) + changes = make([]change, 0) } diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index c40b550e6dc..6304f2f45e3 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -2,16 +2,22 @@ package validators import ( "std" - "strconv" + "gno.land/p/demo/ufmt" "gno.land/p/sys/validators" ) var ( vp validators.ValsetProtocol // p is the underlying validator set protocol - changes []validators.Validator // changes are the set changes that happened between scrapes + changes []change // changes holds any valset changes ) +// change represents a single valset change, tied to a specific block number +type change struct { + blockNum int64 // the block number associated with the valset change + validator validators.Validator // the validator update +} + // addValidator adds a new validator to the validator set. // If the validator is already present, the method errors out func addValidator(validator validators.Validator) { @@ -21,7 +27,10 @@ func addValidator(validator validators.Validator) { } // Validator added, note the change - changes = append(changes, val) + changes = append(changes, change{ + blockNum: std.GetHeight(), + validator: val, + }) // Emit the validator set change std.Emit(validators.ValidatorAddedEvent) @@ -36,10 +45,13 @@ func removeValidator(address std.Address) { } // Validator removed, note the change - changes = append(changes, validators.Validator{ - Address: val.Address, - PubKey: val.PubKey, - VotingPower: 0, // nullified the voting power indicates removal + changes = append(changes, change{ + blockNum: std.GetHeight(), + validator: validators.Validator{ + Address: val.Address, + PubKey: val.PubKey, + VotingPower: 0, // nullified the voting power indicates removal + }, }) // Emit the validator set change @@ -51,9 +63,14 @@ func Render(_ string) string { return "No valset changes to apply." } - output := "Valset changes to apply:\n" + output := "Valset changes:\n" for _, change := range changes { - output += "- " + string(change.Address) + " (" + strconv.FormatUint(change.VotingPower, 10) + ")\n" + output += ufmt.Sprintf( + "- #%d: %s (%d)\n", + change.blockNum, + string(change.validator.Address), + change.validator.VotingPower, + ) } return output diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index d953493bd68..355d1fdadb0 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -118,7 +118,13 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { ) // Set EndBlocker - baseApp.SetEndBlocker(EndBlocker(c, vmKpr, baseApp.Logger())) + baseApp.SetEndBlocker( + EndBlocker( + c, + vmKpr, + baseApp, + ), + ) // Set a handler Route. baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) @@ -219,13 +225,22 @@ func InitChainer( } } +// endBlockerApp is the app abstraction required by any EndBlocker +type endBlockerApp interface { + // LastBlockHeight returns the latest app height + LastBlockHeight() int64 + + // Logger returns the logger reference + Logger() *slog.Logger +} + // EndBlocker defines the logic executed after every block. // Currently, it parses events that happened during execution to calculate // validator set changes func EndBlocker( collector *collector[validatorUpdate], vmKeeper vm.VMKeeperI, - logger *slog.Logger, + app endBlockerApp, ) func( ctx sdk.Context, req abci.RequestEndBlock, @@ -242,11 +257,12 @@ func EndBlocker( Caller: crypto.Address{}, // Zero address PkgPath: valRealm, Func: valChangesFn, + Args: []string{fmt.Sprintf("%d", app.LastBlockHeight())}, } response, err := vmKeeper.Call(ctx, msg) if err != nil { - logger.Error("unable to call VM during EndBlocker", "err", err) + app.Logger().Error("unable to call VM during EndBlocker", "err", err) return abci.ResponseEndBlock{} } @@ -254,7 +270,7 @@ func EndBlocker( // Extract the updates from the VM response updates, err := extractUpdatesFromResponse(response) if err != nil { - logger.Error("unable to extract updates from response", "err", err) + app.Logger().Error("unable to extract updates from response", "err", err) return abci.ResponseEndBlock{} } diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index e29d7675af5..bf3a9d873fc 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -12,7 +12,6 @@ import ( "github.com/gnolang/gno/tm2/pkg/bft/types" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/events" - "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -92,7 +91,7 @@ func TestEndBlocker(t *testing.T) { c := newCollector[validatorUpdate](&mockEventSwitch{}, noFilter) // Create the EndBlocker - eb := EndBlocker(c, nil, log.NewNoopLogger()) + eb := EndBlocker(c, nil, &mockEndBlockerApp{}) // Run the EndBlocker res := eb(sdk.Context{}, abci.RequestEndBlock{}) @@ -133,7 +132,7 @@ func TestEndBlocker(t *testing.T) { mockEventSwitch.FireEvent(std.GnoEvent{}) // Create the EndBlocker - eb := EndBlocker(c, mockVMKeeper, log.NewNoopLogger()) + eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) // Run the EndBlocker res := eb(sdk.Context{}, abci.RequestEndBlock{}) @@ -177,7 +176,7 @@ func TestEndBlocker(t *testing.T) { mockEventSwitch.FireEvent(std.GnoEvent{}) // Create the EndBlocker - eb := EndBlocker(c, mockVMKeeper, log.NewNoopLogger()) + eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) // Run the EndBlocker res := eb(sdk.Context{}, abci.RequestEndBlock{}) @@ -246,7 +245,7 @@ func TestEndBlocker(t *testing.T) { mockEventSwitch.FireEvent(txEvent) // Create the EndBlocker - eb := EndBlocker(c, mockVMKeeper, log.NewNoopLogger()) + eb := EndBlocker(c, mockVMKeeper, &mockEndBlockerApp{}) // Run the EndBlocker res := eb(sdk.Context{}, abci.RequestEndBlock{}) diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go index 214f766b43b..dc55f9d83bf 100644 --- a/gno.land/pkg/gnoland/mock_test.go +++ b/gno.land/pkg/gnoland/mock_test.go @@ -1,8 +1,11 @@ package gnoland import ( + "log/slog" + "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/tm2/pkg/events" + "github.com/gnolang/gno/tm2/pkg/log" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/service" ) @@ -77,3 +80,29 @@ func (m *mockVMKeeper) Run(ctx sdk.Context, msg vm.MsgRun) (res string, err erro return "", nil } + +type ( + lastBlockHeightDelegate func() int64 + loggerDelegate func() *slog.Logger +) + +type mockEndBlockerApp struct { + lastBlockHeightFn lastBlockHeightDelegate + loggerFn loggerDelegate +} + +func (m *mockEndBlockerApp) LastBlockHeight() int64 { + if m.lastBlockHeightFn != nil { + return m.lastBlockHeightFn() + } + + return 0 +} + +func (m *mockEndBlockerApp) Logger() *slog.Logger { + if m.loggerFn != nil { + return m.loggerFn() + } + + return log.NewNoopLogger() +} diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go index 1e9a4e69e6b..ef482a846cb 100644 --- a/gno.land/pkg/gnoland/vals.go +++ b/gno.land/pkg/gnoland/vals.go @@ -9,8 +9,8 @@ import ( ) const ( - valRealm = "gno.land/r/sys/vals" - valChangesFn = "getChanges" + valRealm = "gno.land/r/sys/validators" + valChangesFn = "GetChanges" validatorAddedEvent = "ValidatorAdded" validatorRemovedEvent = "ValidatorRemoved" From 5f5e17247381fc74c2f4c817f114bceb5ebb550a Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Wed, 3 Jul 2024 18:15:42 +0200 Subject: [PATCH 58/71] Use binary search for the changes --- examples/gno.land/r/sys/validators/gnosdk.gno | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno index f598c08bf8a..2626896a529 100644 --- a/examples/gno.land/r/sys/validators/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -1,21 +1,23 @@ package validators import ( + "sort" + "gno.land/p/sys/validators" ) // GetChanges returns the validator changes stored on the realm, since the given block number. // This function is unexported and intended to be called by gno.land through the GnoSDK func GetChanges(from int64) []validators.Validator { - // Construct the changes - valsetChanges := make([]validators.Validator, 0) + // Perform binary search to find the starting index + idx := sort.Search(len(changes), func(i int) bool { + return changes[i].blockNum >= from + }) - for _, change := range changes { - if change.blockNum < from { - continue - } - - valsetChanges = append(valsetChanges, change.validator) + // Construct the changes from the found index + valsetChanges := make([]validators.Validator, 0) + for i := idx; i < len(changes); i++ { + valsetChanges = append(valsetChanges, changes[i].validator) } return valsetChanges From b3231c9487b7dc1959f9f9ceef979b411d15132b Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Thu, 4 Jul 2024 11:15:26 +0200 Subject: [PATCH 59/71] Share event switch --- gno.land/cmd/gnoland/start.go | 8 ++++---- gno.land/pkg/gnoland/app.go | 2 +- gno.land/pkg/gnoland/node_inmemory.go | 5 ++++- tm2/pkg/bft/node/node.go | 15 ++++++++------- tm2/pkg/bft/node/node_test.go | 15 ++++++++------- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index ff5fce02f1c..d7ab96464eb 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -235,11 +235,11 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { return fmt.Errorf("unable to initialize telemetry, %w", err) } - // Create a top-level event switch - eventSwitch := events.NewEventSwitch() + // Create a top-level shared event switch + evsw := events.NewEventSwitch() // Create application and node - cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, logger, eventSwitch) + cfg.LocalApp, err = gnoland.NewApp(nodeDir, c.skipFailingGenesisTxs, evsw, logger) if err != nil { return fmt.Errorf("unable to create the Gnoland app, %w", err) } @@ -250,7 +250,7 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error { } // Create a default node, with the given setup - gnoNode, err := node.DefaultNewNode(cfg, genesisPath, logger) + gnoNode, err := node.DefaultNewNode(cfg, genesisPath, evsw, logger) if err != nil { return fmt.Errorf("unable to create the Gnoland node, %w", err) } diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 355d1fdadb0..6265b9af420 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -148,8 +148,8 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { func NewApp( dataRootDir string, skipFailingGenesisTxs bool, - logger *slog.Logger, evsw events.EventSwitch, + logger *slog.Logger, ) (abci.Application, error) { var err error diff --git a/gno.land/pkg/gnoland/node_inmemory.go b/gno.land/pkg/gnoland/node_inmemory.go index 1e779535b50..003fcf4da4f 100644 --- a/gno.land/pkg/gnoland/node_inmemory.go +++ b/gno.land/pkg/gnoland/node_inmemory.go @@ -85,6 +85,8 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, return nil, fmt.Errorf("validate config error: %w", err) } + evsw := events.NewEventSwitch() + // Initialize the application with the provided options gnoApp, err := NewAppWithOptions(&AppOptions{ Logger: logger, @@ -92,7 +94,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, GenesisTxHandler: cfg.GenesisTxHandler, MaxCycles: cfg.GenesisMaxVMCycles, DB: memdb.NewMemDB(), - EventSwitch: events.NewEventSwitch(), + EventSwitch: evsw, }) if err != nil { return nil, fmt.Errorf("error initializing new app: %w", err) @@ -122,6 +124,7 @@ func NewInMemoryNode(logger *slog.Logger, cfg *InMemoryNodeConfig) (*node.Node, appClientCreator, genProvider, dbProvider, + evsw, logger, ) } diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go index 9dd63464310..e29de3dd1ae 100644 --- a/tm2/pkg/bft/node/node.go +++ b/tm2/pkg/bft/node/node.go @@ -80,7 +80,12 @@ type NodeProvider func(*cfg.Config, *slog.Logger) (*Node, error) // DefaultNewNode returns a Tendermint node with default settings for the // PrivValidator, ClientCreator, GenesisDoc, and DBProvider. // It implements NodeProvider. -func DefaultNewNode(config *cfg.Config, genesisFile string, logger *slog.Logger) (*Node, error) { +func DefaultNewNode( + config *cfg.Config, + genesisFile string, + evsw events.EventSwitch, + logger *slog.Logger, +) (*Node, error) { // Generate node PrivKey nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { @@ -105,6 +110,7 @@ func DefaultNewNode(config *cfg.Config, genesisFile string, logger *slog.Logger) appClientCreator, DefaultGenesisDocProviderFunc(genesisFile), DefaultDBProvider, + evsw, logger, ) } @@ -415,6 +421,7 @@ func NewNode(config *cfg.Config, clientCreator appconn.ClientCreator, genesisDocProvider GenesisDocProvider, dbProvider DBProvider, + evsw events.EventSwitch, logger *slog.Logger, options ...Option, ) (*Node, error) { @@ -434,12 +441,6 @@ func NewNode(config *cfg.Config, return nil, err } - // EventSwitch and EventStoreService must be started before the handshake because - // we might need to store the txs of the replayed block as this might not have happened - // when the node stopped last time (i.e. the node stopped after it saved the block - // but before it indexed the txs, or, endblocker panicked) - evsw := events.NewEventSwitch() - // Signal readiness when receiving the first block. const readinessListenerID = "first_block_listener" diff --git a/tm2/pkg/bft/node/node_test.go b/tm2/pkg/bft/node/node_test.go index 28ab95e0064..e28464ff711 100644 --- a/tm2/pkg/bft/node/node_test.go +++ b/tm2/pkg/bft/node/node_test.go @@ -35,7 +35,7 @@ func TestNodeStartStop(t *testing.T) { defer os.RemoveAll(config.RootDir) // create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewNoopLogger()) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewNoopLogger()) require.NoError(t, err) err = n.Start() require.NoError(t, err) @@ -98,7 +98,7 @@ func TestNodeDelayedStart(t *testing.T) { now := tmtime.Now() // create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) n.GenesisDoc().GenesisTime = now.Add(2 * time.Second) require.NoError(t, err) @@ -115,7 +115,7 @@ func TestNodeReady(t *testing.T) { defer os.RemoveAll(config.RootDir) // Create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) // Assert that blockstore has zero block before waiting for the first block @@ -148,7 +148,7 @@ func TestNodeSetAppVersion(t *testing.T) { defer os.RemoveAll(config.RootDir) // create & start node - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) // default config uses the kvstore app @@ -192,7 +192,7 @@ func TestNodeSetPrivValTCP(t *testing.T) { }() defer signerServer.Stop() - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) assert.IsType(t, &privval.SignerClient{}, n.PrivValidator()) } @@ -205,7 +205,7 @@ func TestPrivValidatorListenAddrNoProtocol(t *testing.T) { defer os.RemoveAll(config.RootDir) config.BaseConfig.PrivValidatorListenAddr = addrNoPrefix - _, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + _, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) assert.Error(t, err) } @@ -236,7 +236,7 @@ func TestNodeSetPrivValIPC(t *testing.T) { }() defer pvsc.Stop() - n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t)) + n, err := DefaultNewNode(config, genesisFile, events.NewEventSwitch(), log.NewTestingLogger(t)) require.NoError(t, err) assert.IsType(t, &privval.SignerClient{}, n.PrivValidator()) } @@ -324,6 +324,7 @@ func TestNodeNewNodeCustomReactors(t *testing.T) { proxy.DefaultClientCreator(nil, config.ProxyApp, config.ABCI, config.DBDir()), DefaultGenesisDocProviderFunc(genesisFile), DefaultDBProvider, + events.NewEventSwitch(), log.NewTestingLogger(t), CustomReactors(map[string]p2p.Reactor{"FOO": cr, "BLOCKCHAIN": customBlockchainReactor}), ) From cafac9ad5275e75d0cd8adeeefd9fece0bc83914 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 5 Jul 2024 17:14:43 +0200 Subject: [PATCH 60/71] Revert to insanity --- tm2/pkg/sdk/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm2/pkg/sdk/baseapp.go b/tm2/pkg/sdk/baseapp.go index 04bc7c347ed..0fa26b817e1 100644 --- a/tm2/pkg/sdk/baseapp.go +++ b/tm2/pkg/sdk/baseapp.go @@ -626,7 +626,7 @@ func (app *BaseApp) runMsgs(ctx Context, msgs []Msg, mode RunTxMode) (result Res data := make([]byte, 0, len(msgs)) err := error(nil) - var events []Event + events := []Event{} // NOTE: GasWanted is determined by ante handler and GasUsed by the GasMeter. for i, msg := range msgs { From 2592b43d21ad55b1039253b2f8adade9778b12a4 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 5 Jul 2024 17:23:41 +0200 Subject: [PATCH 61/71] Rename to vmk --- gno.land/pkg/gnoland/app.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 6265b9af420..4af247b3414 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -86,7 +86,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // XXX: Embed this ? stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") - vmKpr := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) + vmk := vm.NewVMKeeper(baseKey, mainKey, acctKpr, bankKpr, stdlibsDir, cfg.MaxCycles) // Set InitChainer baseApp.SetInitChainer(InitChainer(baseApp, acctKpr, bankKpr, cfg.GenesisTxHandler)) @@ -121,7 +121,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { baseApp.SetEndBlocker( EndBlocker( c, - vmKpr, + vmk, baseApp, ), ) @@ -129,7 +129,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Set a handler Route. baseApp.Router().AddRoute("auth", auth.NewHandler(acctKpr)) baseApp.Router().AddRoute("bank", bank.NewHandler(bankKpr)) - baseApp.Router().AddRoute("vm", vm.NewHandler(vmKpr)) + baseApp.Router().AddRoute("vm", vm.NewHandler(vmk)) // Load latest version. if err := baseApp.LoadLatestVersion(); err != nil { @@ -138,7 +138,7 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Initialize the VMKeeper. ms := baseApp.GetCacheMultiStore() - vmKpr.Initialize(ms) + vmk.Initialize(ms) ms.MultiWrite() // XXX why was't this needed? return baseApp, nil @@ -239,7 +239,7 @@ type endBlockerApp interface { // validator set changes func EndBlocker( collector *collector[validatorUpdate], - vmKeeper vm.VMKeeperI, + vmk vm.VMKeeperI, app endBlockerApp, ) func( ctx sdk.Context, @@ -260,7 +260,7 @@ func EndBlocker( Args: []string{fmt.Sprintf("%d", app.LastBlockHeight())}, } - response, err := vmKeeper.Call(ctx, msg) + response, err := vmk.Call(ctx, msg) if err != nil { app.Logger().Error("unable to call VM during EndBlocker", "err", err) From d1100a764c2857ab3d94ec6e04a4dcb1fbb2875a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 5 Jul 2024 17:26:24 +0200 Subject: [PATCH 62/71] Update gno.land/pkg/gnoland/vals.go Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- gno.land/pkg/gnoland/vals.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go index ef482a846cb..c467778bc1a 100644 --- a/gno.land/pkg/gnoland/vals.go +++ b/gno.land/pkg/gnoland/vals.go @@ -40,7 +40,7 @@ func validatorEventFilter(event events.Event) []validatorUpdate { continue } - // Make sure the event is from `r/sys/vals` + // Make sure the event is from `r/sys/validators` if gnoEv.PkgPath != valRealm { continue } From 120fee9dc689f1c1b3a76e6d9063bfb02e4d32c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20=C5=BDivkovi=C4=87?= Date: Fri, 5 Jul 2024 17:26:34 +0200 Subject: [PATCH 63/71] Update gno.land/pkg/gnoland/vals.go Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> --- gno.land/pkg/gnoland/vals.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go index c467778bc1a..ee9642c4891 100644 --- a/gno.land/pkg/gnoland/vals.go +++ b/gno.land/pkg/gnoland/vals.go @@ -19,7 +19,7 @@ const ( var valRegexp = regexp.MustCompile(`{\("([^"]*)"\s[^)]+\),\("((?:[^"]|\\")*)"\s[^)]+\),\((\d+)\s[^)]+\)}`) // validatorUpdate is a type being used for "notifying" -// that a validator change happened on-chain. The events from `r/sys/vals` +// that a validator change happened on-chain. The events from `r/sys/validators` // do not pass data related to validator add / remove instances (who, what, how) type validatorUpdate struct{} From 60cfdbf11c15838ea08c9f452dcca4472de5fc79 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 5 Jul 2024 17:29:47 +0200 Subject: [PATCH 64/71] Remove eventType --- gno.land/pkg/gnoland/events.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/gno.land/pkg/gnoland/events.go b/gno.land/pkg/gnoland/events.go index 8bc9b788475..ba78c40979e 100644 --- a/gno.land/pkg/gnoland/events.go +++ b/gno.land/pkg/gnoland/events.go @@ -5,23 +5,17 @@ import ( "github.com/rs/xid" ) -// eventType encompasses all event types -// that can appear in the collector -type eventType interface { - validatorUpdate -} - // filterFn is the filter method for incoming events -type filterFn[T eventType] func(events.Event) []T +type filterFn[T any] func(events.Event) []T // collector is the generic in-memory event collector -type collector[T eventType] struct { +type collector[T any] struct { events []T // temporary event storage filter filterFn[T] // method used for filtering events } // newCollector creates a new event collector -func newCollector[T eventType]( +func newCollector[T any]( evsw events.EventSwitch, filter filterFn[T], ) *collector[T] { From e628720623a91c71a5a54f0a3468f3826731b985 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Fri, 5 Jul 2024 17:58:33 +0200 Subject: [PATCH 65/71] Switch to QueryEval instead of MsgCall --- gno.land/pkg/gnoland/app.go | 13 +++++-------- gno.land/pkg/gnoland/app_test.go | 23 +++++++++-------------- gno.land/pkg/gnoland/mock_test.go | 10 ++++++++++ gno.land/pkg/sdk/vm/keeper.go | 1 + 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 4af247b3414..c6ab78337ed 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -253,14 +253,11 @@ func EndBlocker( } // Run the VM to get the updates from the chain - msg := vm.MsgCall{ - Caller: crypto.Address{}, // Zero address - PkgPath: valRealm, - Func: valChangesFn, - Args: []string{fmt.Sprintf("%d", app.LastBlockHeight())}, - } - - response, err := vmk.Call(ctx, msg) + response, err := vmk.QueryEval( + ctx, + valRealm, + fmt.Sprintf("%s(%d)", valChangesFn, app.LastBlockHeight()), + ) if err != nil { app.Logger().Error("unable to call VM during EndBlocker", "err", err) diff --git a/gno.land/pkg/gnoland/app_test.go b/gno.land/pkg/gnoland/app_test.go index bf3a9d873fc..852d090f3af 100644 --- a/gno.land/pkg/gnoland/app_test.go +++ b/gno.land/pkg/gnoland/app_test.go @@ -6,11 +6,9 @@ import ( "strings" "testing" - "github.com/gnolang/gno/gno.land/pkg/sdk/vm" "github.com/gnolang/gno/gnovm/stdlibs/std" abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types" "github.com/gnolang/gno/tm2/pkg/bft/types" - "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/events" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/stretchr/testify/assert" @@ -113,12 +111,11 @@ func TestEndBlocker(t *testing.T) { mockEventSwitch = newCommonEvSwitch() mockVMKeeper = &mockVMKeeper{ - callFn: func(_ sdk.Context, call vm.MsgCall) (string, error) { + queryFn: func(_ sdk.Context, pkgPath, expr string) (string, error) { vmCalled = true - require.Equal(t, crypto.Address{}, call.Caller) - require.Equal(t, valRealm, call.PkgPath) - require.NotNil(t, call.Func) + require.Equal(t, valRealm, pkgPath) + require.NotEmpty(t, expr) return "", errors.New("random call error") }, @@ -157,12 +154,11 @@ func TestEndBlocker(t *testing.T) { mockEventSwitch = newCommonEvSwitch() mockVMKeeper = &mockVMKeeper{ - callFn: func(_ sdk.Context, call vm.MsgCall) (string, error) { + queryFn: func(_ sdk.Context, pkgPath, expr string) (string, error) { vmCalled = true - require.Equal(t, crypto.Address{}, call.Caller) - require.Equal(t, valRealm, call.PkgPath) - require.NotNil(t, call.Func) + require.Equal(t, valRealm, pkgPath) + require.NotEmpty(t, expr) return constructVMResponse([]abci.ValidatorUpdate{}), nil }, @@ -197,10 +193,9 @@ func TestEndBlocker(t *testing.T) { mockEventSwitch = newCommonEvSwitch() mockVMKeeper = &mockVMKeeper{ - callFn: func(_ sdk.Context, call vm.MsgCall) (string, error) { - require.Equal(t, crypto.Address{}, call.Caller) - require.Equal(t, valRealm, call.PkgPath) - require.NotNil(t, call.Func) + queryFn: func(_ sdk.Context, pkgPath, expr string) (string, error) { + require.Equal(t, valRealm, pkgPath) + require.NotEmpty(t, expr) return constructVMResponse(changes), nil }, diff --git a/gno.land/pkg/gnoland/mock_test.go b/gno.land/pkg/gnoland/mock_test.go index dc55f9d83bf..1ff9f168bd1 100644 --- a/gno.land/pkg/gnoland/mock_test.go +++ b/gno.land/pkg/gnoland/mock_test.go @@ -48,12 +48,14 @@ func (m *mockEventSwitch) RemoveListener(listenerID string) { type ( addPackageDelegate func(sdk.Context, vm.MsgAddPackage) error callDelegate func(sdk.Context, vm.MsgCall) (string, error) + queryEvalDelegate func(sdk.Context, string, string) (string, error) runDelegate func(sdk.Context, vm.MsgRun) (string, error) ) type mockVMKeeper struct { addPackageFn addPackageDelegate callFn callDelegate + queryFn queryEvalDelegate runFn runDelegate } @@ -73,6 +75,14 @@ func (m *mockVMKeeper) Call(ctx sdk.Context, msg vm.MsgCall) (res string, err er return "", nil } +func (m *mockVMKeeper) QueryEval(ctx sdk.Context, pkgPath, expr string) (res string, err error) { + if m.queryFn != nil { + return m.queryFn(ctx, pkgPath, expr) + } + + return "", nil +} + func (m *mockVMKeeper) Run(ctx sdk.Context, msg vm.MsgRun) (res string, err error) { if m.runFn != nil { return m.runFn(ctx, msg) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 95f17ce09f0..f4560f8ada6 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -33,6 +33,7 @@ const ( type VMKeeperI interface { AddPackage(ctx sdk.Context, msg MsgAddPackage) error Call(ctx sdk.Context, msg MsgCall) (res string, err error) + QueryEval(ctx sdk.Context, pkgPath string, expr string) (res string, err error) Run(ctx sdk.Context, msg MsgRun) (res string, err error) } From 6c1faddb71f3cb5c67d9b6e7715fcbea69df04e4 Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 5 Jul 2024 20:36:37 +0200 Subject: [PATCH 66/71] Update gno.land/pkg/gnoland/vals.go --- gno.land/pkg/gnoland/vals.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gno.land/pkg/gnoland/vals.go b/gno.land/pkg/gnoland/vals.go index ee9642c4891..1843dff3984 100644 --- a/gno.land/pkg/gnoland/vals.go +++ b/gno.land/pkg/gnoland/vals.go @@ -16,6 +16,7 @@ const ( validatorRemovedEvent = "ValidatorRemoved" ) +// XXX: replace with amino-based clean approach var valRegexp = regexp.MustCompile(`{\("([^"]*)"\s[^)]+\),\("((?:[^"]|\\")*)"\s[^)]+\),\((\d+)\s[^)]+\)}`) // validatorUpdate is a type being used for "notifying" From a16ca4b0548bdbc1cd64154eac0ccaec1abc19ab Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Sat, 6 Jul 2024 13:49:30 +0200 Subject: [PATCH 67/71] Switch to avl.Tree for change management --- examples/gno.land/r/sys/validators/gno.mod | 4 + examples/gno.land/r/sys/validators/gnosdk.gno | 31 +++--- examples/gno.land/r/sys/validators/init.gno | 3 +- .../gno.land/r/sys/validators/validators.gno | 62 ++++++++--- .../r/sys/validators/validators_test.gno | 102 ++++++++++++++++++ 5 files changed, 175 insertions(+), 27 deletions(-) create mode 100644 examples/gno.land/r/sys/validators/validators_test.gno diff --git a/examples/gno.land/r/sys/validators/gno.mod b/examples/gno.land/r/sys/validators/gno.mod index c865861dad5..d9d129dd543 100644 --- a/examples/gno.land/r/sys/validators/gno.mod +++ b/examples/gno.land/r/sys/validators/gno.mod @@ -1,6 +1,10 @@ module gno.land/r/sys/validators require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/uassert v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest gno.land/p/gov/proposal v0.0.0-latest gno.land/p/nt/poa v0.0.0-latest diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno index 2626896a529..ebb26cc7383 100644 --- a/examples/gno.land/r/sys/validators/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -1,24 +1,31 @@ package validators import ( - "sort" - "gno.land/p/sys/validators" ) // GetChanges returns the validator changes stored on the realm, since the given block number. -// This function is unexported and intended to be called by gno.land through the GnoSDK +// This function is intended to be called by gno.land through the GnoSDK func GetChanges(from int64) []validators.Validator { - // Perform binary search to find the starting index - idx := sort.Search(len(changes), func(i int) bool { - return changes[i].blockNum >= from - }) - - // Construct the changes from the found index valsetChanges := make([]validators.Validator, 0) - for i := idx; i < len(changes); i++ { - valsetChanges = append(valsetChanges, changes[i].validator) - } + + // Gather the changes from the specified block + changes.Iterate("", "", func(_ string, value interface{}) bool { + chs := value.([]change) + + if len(chs) > 0 && chs[0].blockNum < from { + // Each change entry in the set is tied to the same block number, + // so there is no need to parse other changes + // if the block number is invalid (< from) + return false + } + + for _, ch := range chs { + valsetChanges = append(valsetChanges, ch.validator) + } + + return false + }) return valsetChanges } diff --git a/examples/gno.land/r/sys/validators/init.gno b/examples/gno.land/r/sys/validators/init.gno index b7c7d1cce4b..0c2af983beb 100644 --- a/examples/gno.land/r/sys/validators/init.gno +++ b/examples/gno.land/r/sys/validators/init.gno @@ -1,6 +1,7 @@ package validators import ( + "gno.land/p/demo/avl" "gno.land/p/nt/poa" ) @@ -9,5 +10,5 @@ func init() { vp = poa.NewPoA() // No changes to apply initially - changes = make([]change, 0) + changes = avl.NewTree() } diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index 6304f2f45e3..3557dcd172f 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -3,13 +3,15 @@ package validators import ( "std" + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" "gno.land/p/demo/ufmt" "gno.land/p/sys/validators" ) var ( vp validators.ValsetProtocol // p is the underlying validator set protocol - changes []change // changes holds any valset changes + changes *avl.Tree // changes holds any valset changes; seqid(block number) -> []change ) // change represents a single valset change, tied to a specific block number @@ -27,10 +29,12 @@ func addValidator(validator validators.Validator) { } // Validator added, note the change - changes = append(changes, change{ + ch := change{ blockNum: std.GetHeight(), validator: val, - }) + } + + saveChange(ch) // Emit the validator set change std.Emit(validators.ValidatorAddedEvent) @@ -45,33 +49,63 @@ func removeValidator(address std.Address) { } // Validator removed, note the change - changes = append(changes, change{ + ch := change{ blockNum: std.GetHeight(), validator: validators.Validator{ Address: val.Address, PubKey: val.PubKey, VotingPower: 0, // nullified the voting power indicates removal }, - }) + } + + saveChange(ch) // Emit the validator set change std.Emit(validators.ValidatorRemovedEvent) } +// saveChange saves the valset change +func saveChange(ch change) { + id := getBlockID(ch.blockNum) + + setRaw, exists := changes.Get(id) + if !exists { + setRaw = make([]change, 0, 1) + } + + // Save the change + set := setRaw.([]change) + set = append(set, ch) + + changes.Set(id, set) +} + +// getBlockID converts the block number to a sequential ID +func getBlockID(blockNum int64) string { + return seqid.ID(uint64(blockNum)).String() +} + func Render(_ string) string { - if len(changes) == 0 { + if changes.Size() == 0 { return "No valset changes to apply." } output := "Valset changes:\n" - for _, change := range changes { - output += ufmt.Sprintf( - "- #%d: %s (%d)\n", - change.blockNum, - string(change.validator.Address), - change.validator.VotingPower, - ) - } + + changes.Iterate("", "", func(_ string, value interface{}) bool { + chs := value.([]change) + + for _, ch := range chs { + output += ufmt.Sprintf( + "- #%d: %s (%d)\n", + ch.blockNum, + ch.validator.Address.String(), + ch.validator.VotingPower, + ) + } + + return false + }) return output } diff --git a/examples/gno.land/r/sys/validators/validators_test.gno b/examples/gno.land/r/sys/validators/validators_test.gno new file mode 100644 index 00000000000..177d84144cb --- /dev/null +++ b/examples/gno.land/r/sys/validators/validators_test.gno @@ -0,0 +1,102 @@ +package validators + +import ( + "testing" + + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" + "gno.land/p/demo/ufmt" + "gno.land/p/sys/validators" +) + +// generateTestValidators generates a dummy validator set +func generateTestValidators(count int) []validators.Validator { + vals := make([]validators.Validator, 0, count) + + for i := 0; i < count; i++ { + val := validators.Validator{ + Address: testutils.TestAddress(ufmt.Sprintf("%d", i)), + PubKey: "public-key", + VotingPower: 10, + } + + vals = append(vals, val) + } + + return vals +} + +func TestValidators_AddRemove(t *testing.T) { + // Clear any changes + changes = avl.NewTree() + + var ( + vals = generateTestValidators(100) + initialHeight = int64(123) + ) + + // Add in the validators + for _, val := range vals { + addValidator(val) + + // Make sure the validator is added + uassert.True(t, vp.IsValidator(val.Address)) + + std.TestSkipHeights(1) + } + + for i := initialHeight; i < initialHeight+int64(len(vals)); i++ { + // Make sure the changes are saved + chs := GetChanges(i) + + // We use the funky index calculation to make sure + // changes are properly handled for each block span + uassert.Equal(t, initialHeight+int64(len(vals))-i, int64(len(chs))) + + for index, val := range vals[i-initialHeight:] { + // Make sure the changes are equal to the additions + ch := chs[index] + + uassert.Equal(t, val.Address, ch.Address) + uassert.Equal(t, val.PubKey, ch.PubKey) + uassert.Equal(t, val.VotingPower, ch.VotingPower) + } + } + + // Save the beginning height for the removal + initialRemoveHeight := std.GetHeight() + + // Clear any changes + changes = avl.NewTree() + + // Remove the validators + for _, val := range vals { + removeValidator(val.Address) + + // Make sure the validator is removed + uassert.False(t, vp.IsValidator(val.Address)) + + std.TestSkipHeights(1) + } + + for i := initialRemoveHeight; i < initialRemoveHeight+int64(len(vals)); i++ { + // Make sure the changes are saved + chs := GetChanges(i) + + // We use the funky index calculation to make sure + // changes are properly handled for each block span + uassert.Equal(t, initialRemoveHeight+int64(len(vals))-i, int64(len(chs))) + + for index, val := range vals[i-initialRemoveHeight:] { + // Make sure the changes are equal to the additions + ch := chs[index] + + uassert.Equal(t, val.Address, ch.Address) + uassert.Equal(t, val.PubKey, ch.PubKey) + uassert.Equal(t, uint64(0), ch.VotingPower) + } + } +} From a027d940ce48200af7d33f5a61a1987c8aecdead Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Sat, 6 Jul 2024 18:19:28 +0200 Subject: [PATCH 68/71] Use starting point for avl traversal --- examples/gno.land/r/sys/validators/gnosdk.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno index ebb26cc7383..21bfb1b1fd8 100644 --- a/examples/gno.land/r/sys/validators/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -10,7 +10,7 @@ func GetChanges(from int64) []validators.Validator { valsetChanges := make([]validators.Validator, 0) // Gather the changes from the specified block - changes.Iterate("", "", func(_ string, value interface{}) bool { + changes.Iterate(getBlockID(from), "", func(_ string, value interface{}) bool { chs := value.([]change) if len(chs) > 0 && chs[0].blockNum < from { From c946718a8f44c24f48eaa18c2e659a12901d9aaf Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Sat, 6 Jul 2024 18:35:12 +0200 Subject: [PATCH 69/71] Simplify saveChange --- examples/gno.land/r/sys/validators/validators.gno | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index 3557dcd172f..c5eced2d008 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -70,7 +70,9 @@ func saveChange(ch change) { setRaw, exists := changes.Get(id) if !exists { - setRaw = make([]change, 0, 1) + changes.Set(id, []change{ch}) + + return } // Save the change From 7be72d23b5582a035348b3e5d918f120689974cb Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Sat, 6 Jul 2024 18:35:50 +0200 Subject: [PATCH 70/71] Remove leftover bounds check --- examples/gno.land/r/sys/validators/gnosdk.gno | 7 ------- 1 file changed, 7 deletions(-) diff --git a/examples/gno.land/r/sys/validators/gnosdk.gno b/examples/gno.land/r/sys/validators/gnosdk.gno index 21bfb1b1fd8..0a85c0682b8 100644 --- a/examples/gno.land/r/sys/validators/gnosdk.gno +++ b/examples/gno.land/r/sys/validators/gnosdk.gno @@ -13,13 +13,6 @@ func GetChanges(from int64) []validators.Validator { changes.Iterate(getBlockID(from), "", func(_ string, value interface{}) bool { chs := value.([]change) - if len(chs) > 0 && chs[0].blockNum < from { - // Each change entry in the set is tied to the same block number, - // so there is no need to parse other changes - // if the block number is invalid (< from) - return false - } - for _, ch := range chs { valsetChanges = append(valsetChanges, ch.validator) } From abc2fc1c5578ea5a39cb2e735f94c26a2c479739 Mon Sep 17 00:00:00 2001 From: Milos Zivkovic Date: Sat, 6 Jul 2024 19:40:44 +0200 Subject: [PATCH 71/71] Display max N changes in the render --- examples/gno.land/r/sys/validators/validators.gno | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/sys/validators/validators.gno b/examples/gno.land/r/sys/validators/validators.gno index c5eced2d008..bf42ece4990 100644 --- a/examples/gno.land/r/sys/validators/validators.gno +++ b/examples/gno.land/r/sys/validators/validators.gno @@ -88,13 +88,17 @@ func getBlockID(blockNum int64) string { } func Render(_ string) string { - if changes.Size() == 0 { + var ( + size = changes.Size() + maxDisplay = 10 + ) + + if size == 0 { return "No valset changes to apply." } output := "Valset changes:\n" - - changes.Iterate("", "", func(_ string, value interface{}) bool { + changes.ReverseIterateByOffset(size-maxDisplay, maxDisplay, func(_ string, value interface{}) bool { chs := value.([]change) for _, ch := range chs {