A very experimental PLC implementation which uses BFT consensus for decentralization
19
fork

Configure Feed

Select the types of activity you want to include in your feed.

Begin work on reputation system

gbl08ma 0fe993f3 f1d776e6

+228 -4
+1 -1
abciapp/block_challenge.go
··· 164 164 } 165 165 defer wtx.Rollback() 166 166 167 - err = store.Consensus.StoreBlockChallengeProof(ctx, wtx, uint64(height), proof) 167 + err = store.Consensus.StoreBlockChallengeProof(wtx, uint64(height), proof) 168 168 if err != nil { 169 169 return nil, stacktrace.Propagate(err, "") 170 170 }
+6
abciapp/range_challenge.go
··· 472 472 473 473 var err error 474 474 for proofHeight, proof := range store.Consensus.BlockChalengeProofsIterator(tx, uint64(max(cacheKey.startHeight-1, 0)), &err) { 475 + select { 476 + case <-ctx.Done(): 477 + return zeroValue, stacktrace.Propagate(ctx.Err(), "") 478 + default: 479 + } 480 + 475 481 if proofHeight > uint64(cacheKey.endHeight) { 476 482 break 477 483 }
+16 -1
abciapp/tx_challenge.go
··· 21 21 const CommitToChallengeMaxRange = 10000 22 22 const CommitToChallengeTargetInterval = 5000 23 23 24 + // TODO adjust these depending on how fast we want inactive validators to lose reputation 25 + // TODO reputation loss (and gain?) should probably be based on a % of the current reputation 26 + // or loss should stack (multiplicatively?) if a validator remains inactive for too long 27 + // so that if a very reputable validator goes offline, it doesn't continue to have a lot of voting power for too long 28 + // perhaps simpler idea: in addition to entropy, apply additional (separate) penalty based on age of last proven height 29 + // (store.Consensus.ValidatorRangeChallengeCompletion) when age crosses some threshold 30 + const ReputationGainPerProvenBlock = 100 31 + const ReputationEntropyLossPerBlock = 90 32 + 24 33 var CommitToChallengeMaxAge = lo.Must(time.ParseDuration("10s")) 25 34 26 35 const CompleteChallengeMaxAgeInBlocks = 4 ··· 318 327 return nil, stacktrace.Propagate(err, "") 319 328 } 320 329 321 - // TODO effects on validator reputation 330 + numProvenBlocks := toHeight - fromHeight + 1 331 + repGain := numProvenBlocks * ReputationGainPerProvenBlock 332 + 333 + err = store.Consensus.ChangeValidatorReputation(writeTx, tx.Arguments.Validator, int64(repGain)) 334 + if err != nil { 335 + return nil, stacktrace.Propagate(err, "") 336 + } 322 337 } 323 338 324 339 return &processResult{
+142 -2
store/consensus.go
··· 8 8 "errors" 9 9 "iter" 10 10 "math" 11 + "math/big" 11 12 "slices" 12 13 "strings" 13 14 "time" ··· 32 33 TreeRangeChallengeKeyLength = 1 + 20 33 34 TreeChallengeCompletionKeyPrefix = 'p' 34 35 TreeChallengeCompletionKeyLength = 1 + 20 36 + TreeValidatorReputationKeyPrefix = 'r' 37 + TreeValidatorReputationKeyLength = 1 + 20 35 38 36 39 IndexBlockChallengeKeyPrefix = 'c' 37 40 IndexBlockChallengeKeyLength = 1 + 8 ··· 75 78 76 79 BlockChallengeProof(tx transaction.Read, height uint64) ([]byte, error) 77 80 BlockChalengeProofsIterator(tx transaction.Read, afterHeight uint64, retErr *error) iter.Seq2[uint64, []byte] // afterHeight is exclusive for consistency with OperationsIterator 78 - StoreBlockChallengeProof(ctx context.Context, tx transaction.WriteIndex, blockHeight uint64, proof []byte) error 81 + StoreBlockChallengeProof(tx transaction.WriteIndex, blockHeight uint64, proof []byte) error 79 82 DeleteBlockChallengeProofsBelowHeight(ctx context.Context, tx transaction.WriteIndex, blockHeight uint64) error 83 + 84 + ValidatorReputation(tx transaction.Read, validatorAddress []byte) (uint64, error) 85 + ChangeValidatorReputation(tx transaction.Write, validatorAddress []byte, change int64) error 86 + ChangeAllNonZeroValidatorReputations(tx transaction.Write, change int64) error 80 87 } 81 88 82 89 var _ ConsensusStore = (*consensusStore)(nil) ··· 792 799 } 793 800 } 794 801 795 - func (t *consensusStore) StoreBlockChallengeProof(ctx context.Context, tx transaction.WriteIndex, blockHeight uint64, proof []byte) error { 802 + func (t *consensusStore) StoreBlockChallengeProof(tx transaction.WriteIndex, blockHeight uint64, proof []byte) error { 796 803 err := tx.IndexDB().Set(marshalBlockChallengeProofKey(blockHeight), proof) 797 804 return stacktrace.Propagate(err, "") 798 805 } ··· 810 817 defer proofsIterator.Close() 811 818 812 819 for proofsIterator.Valid() { 820 + select { 821 + case <-ctx.Done(): 822 + return stacktrace.Propagate(ctx.Err(), "") 823 + default: 824 + } 825 + 813 826 err = tx.IndexDB().Delete(slices.Clone(proofsIterator.Key())) 814 827 if err != nil { 815 828 return stacktrace.Propagate(err, "") ··· 819 832 } 820 833 return nil 821 834 } 835 + 836 + func marshalValidatorReputationKey(validatorAddress []byte) []byte { 837 + key := make([]byte, TreeValidatorReputationKeyLength) 838 + key[0] = TreeValidatorReputationKeyPrefix 839 + copy(key[1:], validatorAddress) 840 + return key 841 + } 842 + 843 + // ValidatorReputation implements [ConsensusStore]. 844 + func (t *consensusStore) ValidatorReputation(tx transaction.Read, validatorAddress []byte) (uint64, error) { 845 + key := marshalValidatorReputationKey(validatorAddress) 846 + 847 + value, err := tx.Tree().Get(key) 848 + if err != nil { 849 + return 0, stacktrace.Propagate(err, "") 850 + } 851 + 852 + // the following returns 0 if value is nil: 853 + return new(big.Int).SetBytes(value).Uint64(), nil 854 + } 855 + 856 + // ChangeValidatorReputation implements [ConsensusStore]. 857 + func (t *consensusStore) ChangeValidatorReputation(tx transaction.Write, validatorAddress []byte, change int64) error { 858 + key := marshalValidatorReputationKey(validatorAddress) 859 + 860 + value, err := tx.Tree().Get(key) 861 + if err != nil { 862 + return stacktrace.Propagate(err, "") 863 + } 864 + 865 + reputation := new(big.Int).SetBytes(value) 866 + reputation.Add(reputation, big.NewInt(change)) 867 + if reputation.Sign() <= 0 { 868 + _, _, err := tx.Tree().Remove(key) 869 + return stacktrace.Propagate(err, "") 870 + } 871 + 872 + _, err = tx.Tree().Set(key, reputation.Bytes()) 873 + return stacktrace.Propagate(err, "") 874 + } 875 + 876 + var minReputationKey = marshalValidatorReputationKey(make([]byte, 20)) 877 + var maxReputationKey = marshalValidatorReputationKey(slices.Repeat([]byte{0xff}, 20)) 878 + 879 + // ChangeAllNonZeroValidatorReputations implements [ConsensusStore]. 880 + func (t *consensusStore) ChangeAllNonZeroValidatorReputations(tx transaction.Write, change int64) error { 881 + // we are not allowed to make updates to the tree while an iterator is active 882 + // process validators in batches of 100 to avoid loading too many key-value pairs into memory 883 + const batchSize = 100 884 + 885 + startingKey := slices.Clone(minReputationKey) 886 + skipFirst := false 887 + 888 + type kv struct { 889 + key []byte 890 + value []byte 891 + } 892 + 893 + changeInt := big.NewInt(change) 894 + 895 + batch := func() ([]kv, bool, error) { 896 + toSet := make([]kv, 0, batchSize) 897 + itr, err := tx.Tree().Iterator(startingKey, maxReputationKey, true) 898 + if err != nil { 899 + return nil, false, stacktrace.Propagate(err, "") 900 + } 901 + defer itr.Close() 902 + 903 + if skipFirst && itr.Valid() { 904 + itr.Next() 905 + } 906 + 907 + for i := 0; itr.Valid() && i < batchSize; i++ { 908 + reputation := new(big.Int).SetBytes(itr.Value()) 909 + reputation.Add(reputation, changeInt) 910 + 911 + if reputation.Sign() <= 0 { 912 + toSet = append(toSet, kv{ 913 + key: slices.Clone(itr.Key()), 914 + value: nil, 915 + }) 916 + } else { 917 + toSet = append(toSet, kv{ 918 + key: slices.Clone(itr.Key()), 919 + value: reputation.Bytes(), 920 + }) 921 + } 922 + 923 + itr.Next() 924 + } 925 + 926 + return toSet, itr.Valid(), nil 927 + } 928 + 929 + for { 930 + toSet, goAgain, err := batch() 931 + if err != nil { 932 + return stacktrace.Propagate(err, "") 933 + } 934 + skipFirst = true 935 + 936 + for _, s := range toSet { 937 + var updated bool 938 + if s.value == nil { 939 + _, updated, err = tx.Tree().Remove(s.key) 940 + if err != nil { 941 + return stacktrace.Propagate(err, "") 942 + } 943 + } else { 944 + updated, err = tx.Tree().Set(s.key, s.value) 945 + if err != nil { 946 + return stacktrace.Propagate(err, "") 947 + } 948 + } 949 + 950 + if !updated { 951 + return stacktrace.NewError("expected to be updating an existing reputation key but didn't") 952 + } 953 + } 954 + 955 + if !goAgain || len(toSet) == 0 { 956 + return nil 957 + } 958 + 959 + startingKey = toSet[len(toSet)-1].key 960 + } 961 + }
+63
store/store_test.go
··· 1 + package store_test 2 + 3 + import ( 4 + "crypto/rand" 5 + "testing" 6 + "time" 7 + 8 + "github.com/stretchr/testify/require" 9 + "tangled.org/gbl08ma.com/didplcbft/store" 10 + "tangled.org/gbl08ma.com/didplcbft/testutil" 11 + ) 12 + 13 + func TestValidatorReputation(t *testing.T) { 14 + txFactory, _, _ := testutil.NewTestTxFactory(t) 15 + 16 + tx, err := txFactory.ReadWorking(time.Now()).Upgrade() 17 + require.NoError(t, err) 18 + 19 + err = store.Consensus.ChangeAllNonZeroValidatorReputations(tx, 100) 20 + require.NoError(t, err) 21 + 22 + validators := make([][]byte, 10) 23 + for i := range validators { 24 + validators[i] = make([]byte, 20) 25 + rand.Read(validators[i]) 26 + } 27 + 28 + for range 3 { 29 + for i := range validators { 30 + err = store.Consensus.ChangeValidatorReputation(tx, validators[i], int64(i)*10) 31 + require.NoError(t, err) 32 + } 33 + } 34 + 35 + for i := range validators { 36 + rep, err := store.Consensus.ValidatorReputation(tx.Downgrade(), validators[i]) 37 + require.NoError(t, err) 38 + require.Equal(t, uint64(3*10*i), rep) 39 + } 40 + 41 + err = store.Consensus.ChangeAllNonZeroValidatorReputations(tx, 100) 42 + require.NoError(t, err) 43 + 44 + for i := range validators { 45 + rep, err := store.Consensus.ValidatorReputation(tx.Downgrade(), validators[i]) 46 + require.NoError(t, err) 47 + if i == 0 { 48 + // validators[0] did not have a NonZero reputation, so we don't expect it to change 49 + require.Zero(t, rep) 50 + } else { 51 + require.Equal(t, uint64(100+3*10*i), rep) 52 + } 53 + } 54 + 55 + err = store.Consensus.ChangeAllNonZeroValidatorReputations(tx, -1000) 56 + require.NoError(t, err) 57 + 58 + for i := range validators { 59 + rep, err := store.Consensus.ValidatorReputation(tx.Downgrade(), validators[i]) 60 + require.NoError(t, err) 61 + require.Zero(t, rep) 62 + } 63 + }