diff --git a/x/tally/keeper/abci.go b/x/tally/keeper/abci.go index e0b9f8d1..7ce3b4e5 100644 --- a/x/tally/keeper/abci.go +++ b/x/tally/keeper/abci.go @@ -160,7 +160,7 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request) (tallyvm.VmRe return tallyvm.VmResult{}, false, errorsmod.Wrap(err, "failed to decode consensus filter") } - // Sort reveals. + // Sort reveals and proxy public keys. keys := make([]string, len(req.Reveals)) i := 0 for k := range req.Reveals { @@ -171,6 +171,7 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request) (tallyvm.VmRe reveals := make([]types.RevealBody, len(req.Reveals)) for i, k := range keys { reveals[i] = req.Reveals[k] + sort.Strings(reveals[i].ProxyPubKeys) } outliers, consensus, err := ApplyFilter(filter, reveals) diff --git a/x/tally/keeper/filter.go b/x/tally/keeper/filter.go index 5a4e56c1..677487de 100644 --- a/x/tally/keeper/filter.go +++ b/x/tally/keeper/filter.go @@ -2,6 +2,7 @@ package keeper import ( "errors" + "fmt" "github.com/sedaprotocol/seda-chain/x/tally/types" ) @@ -15,12 +16,30 @@ const ( // ApplyFilter processes filter of the type specified in the first // byte of consensus filter. It returns an outlier list, which is // a boolean list where true at index i means that the reveal at -// index i is an outlier, consensus boolean, and error. +// index i is an outlier, consensus boolean, and error. It assumes +// that the reveals and their proxy public keys are sorted. func ApplyFilter(input []byte, reveals []types.RevealBody) ([]int, bool, error) { if len(input) == 0 { return make([]int, len(reveals)), false, types.ErrInvalidFilterType } + // Determine consensus on tuple of (exit_code, proxy_pub_keys) + var maxFreq int + freq := make(map[string]int, len(reveals)) + for _, reveal := range reveals { + var success bool + if reveal.ExitCode != 0 { + success = false + } + + tuple := fmt.Sprintf("%v%v", success, reveal.ProxyPubKeys) + freq[tuple]++ + maxFreq = max(freq[tuple], maxFreq) + } + if maxFreq*3 < len(reveals)*2 { + return make([]int, len(reveals)), false, types.ErrNoBasicConsensus + } + var filter types.Filter var err error switch input[0] { diff --git a/x/tally/types/abci_types.go b/x/tally/types/abci_types.go index e865dc5c..55bc12d8 100644 --- a/x/tally/types/abci_types.go +++ b/x/tally/types/abci_types.go @@ -24,10 +24,11 @@ type Request struct { } type RevealBody struct { - Salt []byte `json:"salt"` - ExitCode byte `json:"exit_code"` - GasUsed string `json:"gas_used"` - Reveal string `json:"reveal"` // base64-encoded string + Salt []byte `json:"salt"` + ExitCode byte `json:"exit_code"` + GasUsed string `json:"gas_used"` + Reveal string `json:"reveal"` // base64-encoded string + ProxyPubKeys []string `json:"proxy_public_keys"` } func (u *RevealBody) MarshalJSON() ([]byte, error) { diff --git a/x/tally/types/errors.go b/x/tally/types/errors.go index c7e6cde0..30bd168e 100644 --- a/x/tally/types/errors.go +++ b/x/tally/types/errors.go @@ -7,8 +7,9 @@ var ( ErrFilterInputTooShort = errors.Register("tally", 3, "filter input length too short") ErrInvalidPathLen = errors.Register("tally", 4, "invalid JSON path length") ErrEmptyReveals = errors.Register("tally", 5, "no reveals given") - ErrCorruptReveals = errors.Register("tally", 6, "more than 1/3 of the reveals are corrupted") - ErrNoConsensus = errors.Register("tally", 7, "1/3 or more of the reveals are not in consensus range") - ErrInvalidNumberType = errors.Register("tally", 8, "invalid number type specified") - ErrFilterUnexpected = errors.Register("tally", 9, "unexpected error occurred in filter") + ErrCorruptReveals = errors.Register("tally", 6, "> 1/3 of reveals are corrupted") + ErrNoConsensus = errors.Register("tally", 7, "> 1/3 of reveals do not agree on reveal data") + ErrNoBasicConsensus = errors.Register("tally", 8, "> 1/3 of reveals do not agree on (exit_code, proxy_pub_keys)") + ErrInvalidNumberType = errors.Register("tally", 9, "invalid number type specified") + ErrFilterUnexpected = errors.Register("tally", 10, "unexpected error occurred in filter") ) diff --git a/x/tally/types/filters.go b/x/tally/types/filters.go index fb5a246e..d9ae1d34 100644 --- a/x/tally/types/filters.go +++ b/x/tally/types/filters.go @@ -3,10 +3,8 @@ package types import ( "bytes" "encoding/binary" - - "slices" - "golang.org/x/exp/constraints" + "slices" ) type Filter interface { diff --git a/x/tally/types/filters_util.go b/x/tally/types/filters_util.go index 9c9a87d5..87cea5ee 100644 --- a/x/tally/types/filters_util.go +++ b/x/tally/types/filters_util.go @@ -28,11 +28,6 @@ func parseReveals(reveals []RevealBody, dataPath string) ([]any, dataAttributes, freq := make(map[any]int, len(reveals)) dataList := make([]any, len(reveals)) for i, r := range reveals { - if r.ExitCode != 0 { - corruptCount++ - continue - } - revealBytes, err := base64.StdEncoding.DecodeString(r.Reveal) if err != nil { corruptCount++