···4444 aocsByPLC map[string]*authoritativeOperationsCache
45454646 blockChallengeCoordinator *blockChallengeCoordinator
4747+ rangeChallengeCoordinator *rangeChallengeCoordinator
4748}
48494950// store and plc must be able to share transaction objects
···193194 if err != nil {
194195 return stacktrace.Propagate(err, "")
195196 }
197197+198198+ d.rangeChallengeCoordinator, err = newRangeChallengeCoordinator(d.runnerContext, d.txFactory, blockStore, pubKey)
199199+ if err != nil {
200200+ return stacktrace.Propagate(err, "")
201201+ }
202202+196203 return nil
197204}
198205
+1-1
abciapp/block_challenge.go
···81818282 anyTx := ctx.Value(contextTxKey{})
8383 if anyTx == nil {
8484- return theine.Loaded[proof.BlockChallengeCircuit]{}, stacktrace.NewError("transaction not found in context")
8484+ return zeroValue, stacktrace.NewError("transaction not found in context")
8585 }
8686 tx, ok := anyTx.(transaction.Read)
8787 if !ok {
+160
abciapp/range_challenge.go
···11+package abciapp
22+33+import (
44+ "bytes"
55+ "context"
66+ "encoding/binary"
77+ "slices"
88+99+ "github.com/Yiling-J/theine-go"
1010+ "github.com/cometbft/cometbft/crypto"
1111+ bftstore "github.com/cometbft/cometbft/store"
1212+ "github.com/cosmos/iavl"
1313+ "github.com/cosmos/iavl/db"
1414+ ics23 "github.com/cosmos/ics23/go"
1515+ "github.com/palantir/stacktrace"
1616+ "tangled.org/gbl08ma.com/didplcbft/store"
1717+ "tangled.org/gbl08ma.com/didplcbft/transaction"
1818+)
1919+2020+type rangeChallengeCoordinator struct {
2121+ runnerContext context.Context
2222+2323+ validatorAddress []byte
2424+ txFactory *transaction.Factory
2525+ nodeBlockStore *bftstore.BlockStore
2626+2727+ treeCache *theine.LoadingCache[treeCacheKey, cachedTree]
2828+}
2929+3030+func newRangeChallengeCoordinator(runnerContext context.Context, txFactory *transaction.Factory, blockStore *bftstore.BlockStore, pubKey crypto.PubKey) (*rangeChallengeCoordinator, error) {
3131+ c := &rangeChallengeCoordinator{
3232+ txFactory: txFactory,
3333+ runnerContext: runnerContext,
3434+ nodeBlockStore: blockStore,
3535+ validatorAddress: pubKey.Address(),
3636+ }
3737+3838+ var err error
3939+ c.treeCache, err = theine.NewBuilder[treeCacheKey, cachedTree](2).Loading(c.proofTreeLoader).Build()
4040+ if err != nil {
4141+ return nil, stacktrace.Propagate(err, "")
4242+ }
4343+4444+ return c, nil
4545+}
4646+4747+type treeCacheKey struct {
4848+ startHeight int64
4949+ endHeight int64
5050+}
5151+5252+type cachedTree struct {
5353+ tree *iavl.ImmutableTree
5454+ root []byte
5555+}
5656+5757+type rangeChallengeProof struct {
5858+ treeRoot []byte // the tree root we commit to. must be the same between the "commit proof" and the "confirmation proof"
5959+ membershipProof *ics23.CommitmentProof
6060+}
6161+6262+func (c *rangeChallengeCoordinator) computeRangeChallengeProof(ctx context.Context, tx transaction.Read, startHeight, endHeight, proveHeight int64) (rangeChallengeProof, error) {
6363+ ctx = context.WithValue(ctx, contextTxKey{}, tx)
6464+ ct, err := c.treeCache.Get(ctx, treeCacheKey{
6565+ startHeight: startHeight,
6666+ endHeight: endHeight,
6767+ })
6868+ if err != nil {
6969+ return rangeChallengeProof{}, stacktrace.Propagate(err, "")
7070+ }
7171+7272+ proofKey := binary.BigEndian.AppendUint64(nil, uint64(proveHeight))
7373+7474+ membershipProof, err := ct.tree.GetMembershipProof(proofKey)
7575+ if err != nil {
7676+ return rangeChallengeProof{}, stacktrace.Propagate(err, "")
7777+ }
7878+7979+ return rangeChallengeProof{
8080+ treeRoot: ct.root,
8181+ membershipProof: membershipProof,
8282+ }, nil
8383+}
8484+8585+func verifyMembershipOfRangeChallengeProofs(ctx context.Context, proofs ...rangeChallengeProof) (bool, error) {
8686+ if len(proofs) < 1 {
8787+ return false, stacktrace.NewError("insufficient proofs")
8888+ }
8989+9090+ treeRoot := proofs[0].treeRoot
9191+9292+ for _, proof := range proofs {
9393+ if !bytes.Equal(proof.treeRoot, treeRoot) {
9494+ // did not commit to the same root in all proofs
9595+ return false, nil
9696+ }
9797+9898+ // just use the key and value claimed in the proof as those should have been validated previously
9999+ if exist := proof.membershipProof.GetExist(); exist != nil {
100100+ if !ics23.VerifyMembership(ics23.IavlSpec, treeRoot, proof.membershipProof, exist.Key, exist.Value) {
101101+ return false, nil
102102+ }
103103+ } else {
104104+ return false, stacktrace.NewError("proof is not an existence proof")
105105+ }
106106+ }
107107+108108+ return true, nil
109109+}
110110+111111+func (c *rangeChallengeCoordinator) proofTreeLoader(ctx context.Context, cacheKey treeCacheKey) (theine.Loaded[cachedTree], error) {
112112+ var zeroValue theine.Loaded[cachedTree]
113113+114114+ anyTx := ctx.Value(contextTxKey{})
115115+ if anyTx == nil {
116116+ return zeroValue, stacktrace.NewError("transaction not found in context")
117117+ }
118118+ tx, ok := anyTx.(transaction.Read)
119119+ if !ok {
120120+ return zeroValue, stacktrace.NewError("invalid transaction in context")
121121+ }
122122+123123+ tree := iavl.NewMutableTree(db.NewMemDB(), 16, false, iavl.NewNopLogger(), iavl.AsyncPruningOption(false))
124124+125125+ var err error
126126+ for proofHeight, proof := range store.Consensus.BlockChalengeProofsIterator(tx, uint64(max(cacheKey.startHeight-1, 0)), &err) {
127127+ if proofHeight > uint64(cacheKey.endHeight) {
128128+ break
129129+ }
130130+131131+ _, err := tree.Set(binary.BigEndian.AppendUint64(nil, proofHeight), slices.Clone(proof))
132132+ if err != nil {
133133+ return zeroValue, stacktrace.Propagate(err, "")
134134+ }
135135+ }
136136+ if err != nil {
137137+ return zeroValue, stacktrace.Propagate(err, "")
138138+ }
139139+140140+ rootHash, treeVersion, err := tree.SaveVersion()
141141+ if err != nil {
142142+ return zeroValue, stacktrace.Propagate(err, "")
143143+ }
144144+145145+ immutableTree, err := tree.GetImmutable(treeVersion)
146146+ if err != nil {
147147+ return zeroValue, stacktrace.Propagate(err, "")
148148+ }
149149+150150+ if immutableTree.Size() != cacheKey.endHeight-cacheKey.startHeight+1 {
151151+ return zeroValue, stacktrace.NewError("missing block challenge proofs in requested range")
152152+ }
153153+154154+ return theine.Loaded[cachedTree]{
155155+ Value: cachedTree{
156156+ tree: immutableTree,
157157+ root: rootHash,
158158+ },
159159+ }, nil
160160+}