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 range challenges

gbl08ma 686b6364 c64bc7d9

+288 -1
+7
abciapp/app.go
··· 44 44 aocsByPLC map[string]*authoritativeOperationsCache 45 45 46 46 blockChallengeCoordinator *blockChallengeCoordinator 47 + rangeChallengeCoordinator *rangeChallengeCoordinator 47 48 } 48 49 49 50 // store and plc must be able to share transaction objects ··· 193 194 if err != nil { 194 195 return stacktrace.Propagate(err, "") 195 196 } 197 + 198 + d.rangeChallengeCoordinator, err = newRangeChallengeCoordinator(d.runnerContext, d.txFactory, blockStore, pubKey) 199 + if err != nil { 200 + return stacktrace.Propagate(err, "") 201 + } 202 + 196 203 return nil 197 204 } 198 205
+1 -1
abciapp/block_challenge.go
··· 81 81 82 82 anyTx := ctx.Value(contextTxKey{}) 83 83 if anyTx == nil { 84 - return theine.Loaded[proof.BlockChallengeCircuit]{}, stacktrace.NewError("transaction not found in context") 84 + return zeroValue, stacktrace.NewError("transaction not found in context") 85 85 } 86 86 tx, ok := anyTx.(transaction.Read) 87 87 if !ok {
+160
abciapp/range_challenge.go
··· 1 + package abciapp 2 + 3 + import ( 4 + "bytes" 5 + "context" 6 + "encoding/binary" 7 + "slices" 8 + 9 + "github.com/Yiling-J/theine-go" 10 + "github.com/cometbft/cometbft/crypto" 11 + bftstore "github.com/cometbft/cometbft/store" 12 + "github.com/cosmos/iavl" 13 + "github.com/cosmos/iavl/db" 14 + ics23 "github.com/cosmos/ics23/go" 15 + "github.com/palantir/stacktrace" 16 + "tangled.org/gbl08ma.com/didplcbft/store" 17 + "tangled.org/gbl08ma.com/didplcbft/transaction" 18 + ) 19 + 20 + type rangeChallengeCoordinator struct { 21 + runnerContext context.Context 22 + 23 + validatorAddress []byte 24 + txFactory *transaction.Factory 25 + nodeBlockStore *bftstore.BlockStore 26 + 27 + treeCache *theine.LoadingCache[treeCacheKey, cachedTree] 28 + } 29 + 30 + func newRangeChallengeCoordinator(runnerContext context.Context, txFactory *transaction.Factory, blockStore *bftstore.BlockStore, pubKey crypto.PubKey) (*rangeChallengeCoordinator, error) { 31 + c := &rangeChallengeCoordinator{ 32 + txFactory: txFactory, 33 + runnerContext: runnerContext, 34 + nodeBlockStore: blockStore, 35 + validatorAddress: pubKey.Address(), 36 + } 37 + 38 + var err error 39 + c.treeCache, err = theine.NewBuilder[treeCacheKey, cachedTree](2).Loading(c.proofTreeLoader).Build() 40 + if err != nil { 41 + return nil, stacktrace.Propagate(err, "") 42 + } 43 + 44 + return c, nil 45 + } 46 + 47 + type treeCacheKey struct { 48 + startHeight int64 49 + endHeight int64 50 + } 51 + 52 + type cachedTree struct { 53 + tree *iavl.ImmutableTree 54 + root []byte 55 + } 56 + 57 + type rangeChallengeProof struct { 58 + treeRoot []byte // the tree root we commit to. must be the same between the "commit proof" and the "confirmation proof" 59 + membershipProof *ics23.CommitmentProof 60 + } 61 + 62 + func (c *rangeChallengeCoordinator) computeRangeChallengeProof(ctx context.Context, tx transaction.Read, startHeight, endHeight, proveHeight int64) (rangeChallengeProof, error) { 63 + ctx = context.WithValue(ctx, contextTxKey{}, tx) 64 + ct, err := c.treeCache.Get(ctx, treeCacheKey{ 65 + startHeight: startHeight, 66 + endHeight: endHeight, 67 + }) 68 + if err != nil { 69 + return rangeChallengeProof{}, stacktrace.Propagate(err, "") 70 + } 71 + 72 + proofKey := binary.BigEndian.AppendUint64(nil, uint64(proveHeight)) 73 + 74 + membershipProof, err := ct.tree.GetMembershipProof(proofKey) 75 + if err != nil { 76 + return rangeChallengeProof{}, stacktrace.Propagate(err, "") 77 + } 78 + 79 + return rangeChallengeProof{ 80 + treeRoot: ct.root, 81 + membershipProof: membershipProof, 82 + }, nil 83 + } 84 + 85 + func verifyMembershipOfRangeChallengeProofs(ctx context.Context, proofs ...rangeChallengeProof) (bool, error) { 86 + if len(proofs) < 1 { 87 + return false, stacktrace.NewError("insufficient proofs") 88 + } 89 + 90 + treeRoot := proofs[0].treeRoot 91 + 92 + for _, proof := range proofs { 93 + if !bytes.Equal(proof.treeRoot, treeRoot) { 94 + // did not commit to the same root in all proofs 95 + return false, nil 96 + } 97 + 98 + // just use the key and value claimed in the proof as those should have been validated previously 99 + if exist := proof.membershipProof.GetExist(); exist != nil { 100 + if !ics23.VerifyMembership(ics23.IavlSpec, treeRoot, proof.membershipProof, exist.Key, exist.Value) { 101 + return false, nil 102 + } 103 + } else { 104 + return false, stacktrace.NewError("proof is not an existence proof") 105 + } 106 + } 107 + 108 + return true, nil 109 + } 110 + 111 + func (c *rangeChallengeCoordinator) proofTreeLoader(ctx context.Context, cacheKey treeCacheKey) (theine.Loaded[cachedTree], error) { 112 + var zeroValue theine.Loaded[cachedTree] 113 + 114 + anyTx := ctx.Value(contextTxKey{}) 115 + if anyTx == nil { 116 + return zeroValue, stacktrace.NewError("transaction not found in context") 117 + } 118 + tx, ok := anyTx.(transaction.Read) 119 + if !ok { 120 + return zeroValue, stacktrace.NewError("invalid transaction in context") 121 + } 122 + 123 + tree := iavl.NewMutableTree(db.NewMemDB(), 16, false, iavl.NewNopLogger(), iavl.AsyncPruningOption(false)) 124 + 125 + var err error 126 + for proofHeight, proof := range store.Consensus.BlockChalengeProofsIterator(tx, uint64(max(cacheKey.startHeight-1, 0)), &err) { 127 + if proofHeight > uint64(cacheKey.endHeight) { 128 + break 129 + } 130 + 131 + _, err := tree.Set(binary.BigEndian.AppendUint64(nil, proofHeight), slices.Clone(proof)) 132 + if err != nil { 133 + return zeroValue, stacktrace.Propagate(err, "") 134 + } 135 + } 136 + if err != nil { 137 + return zeroValue, stacktrace.Propagate(err, "") 138 + } 139 + 140 + rootHash, treeVersion, err := tree.SaveVersion() 141 + if err != nil { 142 + return zeroValue, stacktrace.Propagate(err, "") 143 + } 144 + 145 + immutableTree, err := tree.GetImmutable(treeVersion) 146 + if err != nil { 147 + return zeroValue, stacktrace.Propagate(err, "") 148 + } 149 + 150 + if immutableTree.Size() != cacheKey.endHeight-cacheKey.startHeight+1 { 151 + return zeroValue, stacktrace.NewError("missing block challenge proofs in requested range") 152 + } 153 + 154 + return theine.Loaded[cachedTree]{ 155 + Value: cachedTree{ 156 + tree: immutableTree, 157 + root: rootHash, 158 + }, 159 + }, nil 160 + }
+34
abciapp/tx.go
··· 5 5 "context" 6 6 7 7 abcitypes "github.com/cometbft/cometbft/abci/types" 8 + "github.com/cometbft/cometbft/crypto" 8 9 cbornode "github.com/ipfs/go-ipld-cbor" 9 10 "github.com/palantir/stacktrace" 10 11 "github.com/samber/mo" ··· 46 47 type Transaction[ArgType any] struct { 47 48 Action TransactionAction `json:"action" refmt:"action"` 48 49 Arguments ArgType `json:"arguments,omitempty" refmt:"arguments,omitempty"` 50 + 51 + // Only some operations require this. The signature is verified against a context-dependent identity (usually specified within Arguments) 52 + Signature []byte `json:"sig,omitempty" refmt:"sig,omitempty"` 49 53 } 50 54 51 55 func UnmarshalTransaction[ArgType ArgumentType](txBytes []byte) (Transaction[ArgType], error) { ··· 83 87 return false 84 88 } 85 89 return bytes.Equal(txBytes, s) 90 + } 91 + 92 + func SignTransaction[ArgType ArgumentType](privKey crypto.PrivKey, tx Transaction[ArgType]) (Transaction[ArgType], error) { 93 + var zeroValue Transaction[ArgType] 94 + 95 + tx.Signature = nil 96 + 97 + bytesToSign, err := cbornode.DumpObject(tx) 98 + if err != nil { 99 + return zeroValue, stacktrace.Propagate(err, "") 100 + } 101 + 102 + tx.Signature, err = privKey.Sign(bytesToSign) 103 + if err != nil { 104 + return zeroValue, stacktrace.Propagate(err, "") 105 + } 106 + 107 + return tx, nil 108 + } 109 + 110 + func VerifyTransactionSignature[ArgType ArgumentType](publicKey crypto.PubKey, tx Transaction[ArgType]) (bool, error) { 111 + sig := tx.Signature 112 + tx.Signature = nil 113 + 114 + bytesToSign, err := cbornode.DumpObject(tx) 115 + if err != nil { 116 + return false, stacktrace.Propagate(err, "") 117 + } 118 + 119 + return publicKey.VerifySignature(bytesToSign, sig), nil 86 120 } 87 121 88 122 type processResult struct {
+86
abciapp/tx_challenge.go
··· 1 + package abciapp 2 + 3 + import ( 4 + "context" 5 + 6 + "github.com/cometbft/cometbft/crypto" 7 + cmtjson "github.com/cometbft/cometbft/libs/json" 8 + cbornode "github.com/ipfs/go-ipld-cbor" 9 + "github.com/palantir/stacktrace" 10 + ) 11 + 12 + var TransactionActionCommitToChallenge = registerTransactionAction[CommitToChallengeArguments]("CommitToChallenge", processCommitToChallengeTx) 13 + 14 + type CommitToChallengeArguments struct { 15 + ValidatorPubKey []byte `json:"validator" refmt:"validator"` 16 + FromHeight int64 `json:"fromHeight" refmt:"fromHeight"` 17 + 18 + // ToHeight should not be more than N blocks behind the block where this transaction is included 19 + // (i.e. this transaction "expires" once ToHeight is more than N blocks old, and shouldn't be included after that) 20 + // TODO determine N 21 + ToHeight int64 `json:"toHeight" refmt:"toHeight"` 22 + Root []byte `json:"root" refmt:"root"` 23 + Proof []byte `json:"proof" refmt:"proof"` 24 + } 25 + 26 + func (CommitToChallengeArguments) ForAction() TransactionAction { 27 + return TransactionActionCommitToChallenge 28 + } 29 + 30 + func init() { 31 + cbornode.RegisterCborType(CommitToChallengeArguments{}) 32 + cbornode.RegisterCborType(Transaction[CommitToChallengeArguments]{}) 33 + } 34 + 35 + func processCommitToChallengeTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte) (*processResult, error) { 36 + tx, err := UnmarshalTransaction[CommitToChallengeArguments](txBytes) 37 + if err != nil { 38 + return &processResult{ 39 + Code: 4000, 40 + Info: err.Error(), 41 + }, nil 42 + } 43 + 44 + var pubKey crypto.PubKey 45 + err = cmtjson.Unmarshal(tx.Arguments.ValidatorPubKey, &pubKey) 46 + 47 + verified, err := VerifyTransactionSignature(pubKey, tx) 48 + if err != nil { 49 + return nil, stacktrace.Propagate(err, "") 50 + } 51 + 52 + if !verified { 53 + return &processResult{ 54 + Code: 4200, 55 + Info: "invalid signature", 56 + }, nil 57 + } 58 + 59 + return nil, stacktrace.NewError("not implemented") // TODO 60 + } 61 + 62 + var TransactionActionCompleteChallenge = registerTransactionAction[CommitToChallengeArguments]("CompleteChallenge", processCompleteChallengeTx) 63 + 64 + type CompleteChallengeArguments struct { 65 + Validator []byte `json:"validator" refmt:"validator"` 66 + 67 + // this shall be a membership proof on the same tree the validator previously committed to, 68 + // for the key deterministically-randomly determined by the last_commit_hash of the block _after_ the one 69 + // where the most recent CommitToChallenge transaction for this validator got included 70 + // this transaction must be included within N blocks after the inclusion of that CommitToChallenge transaction 71 + // TODO determine N 72 + Proof []byte `json:"proof" refmt:"proof"` 73 + } 74 + 75 + func (CompleteChallengeArguments) ForAction() TransactionAction { 76 + return TransactionActionCommitToChallenge 77 + } 78 + 79 + func init() { 80 + cbornode.RegisterCborType(CompleteChallengeArguments{}) 81 + cbornode.RegisterCborType(Transaction[CompleteChallengeArguments]{}) 82 + } 83 + 84 + func processCompleteChallengeTx(ctx context.Context, deps TransactionProcessorDependencies, txBytes []byte) (*processResult, error) { 85 + return nil, stacktrace.NewError("not implemented") // TODO 86 + }