github.com/consensys/gnark@v0.11.0/constraint/blueprint_logderivlookup.go (about) 1 package constraint 2 3 import ( 4 "fmt" 5 "sync" 6 ) 7 8 // TODO @gbotrel this shouldn't be there, but we need to figure out a clean way to serialize 9 // blueprints 10 11 // BlueprintLookupHint is a blueprint that facilitates the lookup of values in a table. 12 // It is essentially a hint to the solver, but enables storing the table entries only once. 13 type BlueprintLookupHint struct { 14 EntriesCalldata []uint32 15 16 // stores the maxLevel of the entries computed by WireWalker 17 maxLevel Level 18 maxLevelPosition int 19 maxLevelOffset int 20 21 // cache the resolved entries by the solver 22 cachedEntries []Element 23 cachedOffset int 24 lock sync.Mutex 25 } 26 27 // ensures BlueprintLookupHint implements the BlueprintStateful interface 28 var _ BlueprintStateful = (*BlueprintLookupHint)(nil) 29 30 func (b *BlueprintLookupHint) Solve(s Solver, inst Instruction) error { 31 nbEntries := int(inst.Calldata[1]) 32 33 // check if we already cached the entries 34 b.lock.Lock() 35 if len(b.cachedEntries) < nbEntries { 36 // we need to cache more entries 37 offset, delta := b.cachedOffset, 0 38 for i := len(b.cachedEntries); i < nbEntries; i++ { 39 b.cachedEntries = append(b.cachedEntries, Element{}) 40 b.cachedEntries[i], delta = s.Read(b.EntriesCalldata[offset:]) 41 offset += delta 42 } 43 b.cachedOffset = offset 44 } 45 b.lock.Unlock() 46 47 // we only append to the entries and never resize the slice; so we can access these indices safely 48 entries := b.cachedEntries[:nbEntries] 49 50 nbInputs := int(inst.Calldata[2]) 51 52 // read the inputs from the instruction 53 inputs := make([]Element, nbInputs) 54 offset, delta := 3, 0 55 for i := 0; i < nbInputs; i++ { 56 inputs[i], delta = s.Read(inst.Calldata[offset:]) 57 offset += delta 58 } 59 60 // set the outputs 61 nbOutputs := nbInputs 62 63 for i := 0; i < nbOutputs; i++ { 64 idx, isUint64 := s.Uint64(inputs[i]) 65 if !isUint64 || idx >= uint64(len(entries)) { 66 return fmt.Errorf("lookup query too large") 67 } 68 // we set the output wire to the value of the entry 69 s.SetValue(uint32(i+int(inst.WireOffset)), entries[idx]) 70 } 71 return nil 72 } 73 74 func (b *BlueprintLookupHint) Reset() { 75 // first we need to compute the capacity; that is 1 element per linear expression in the entries. 76 // this must be accurate since solver is multi threaded and we don't want to resize the slice 77 // while the solver is running. 78 capacity := 0 79 for i := 0; i < len(b.EntriesCalldata); i++ { 80 n := int(b.EntriesCalldata[i]) // length of the linear expression 81 capacity++ 82 i += 2 * n // skip the linear expression 83 } 84 85 b.cachedEntries = make([]Element, 0, capacity) 86 b.cachedOffset = 0 87 } 88 89 func (b *BlueprintLookupHint) CalldataSize() int { 90 // variable size 91 return -1 92 } 93 func (b *BlueprintLookupHint) NbConstraints() int { 94 return 0 95 } 96 97 // NbOutputs return the number of output wires this blueprint creates. 98 func (b *BlueprintLookupHint) NbOutputs(inst Instruction) int { 99 return int(inst.Calldata[2]) 100 } 101 102 func (b *BlueprintLookupHint) UpdateInstructionTree(inst Instruction, tree InstructionTree) Level { 103 // depend on the table UP to the number of entries at time of instruction creation. 104 nbEntries := int(inst.Calldata[1]) 105 106 // check if we already cached the max level 107 if b.maxLevelPosition-1 < nbEntries { // adjust for default value of b.maxLevelPosition (0) 108 109 j := b.maxLevelOffset // skip the entries we already processed 110 for i := b.maxLevelPosition; i < nbEntries; i++ { 111 // first we have the length of the linear expression 112 n := int(b.EntriesCalldata[j]) 113 j++ 114 for k := 0; k < n; k++ { 115 wireID := b.EntriesCalldata[j+1] 116 j += 2 117 if !tree.HasWire(wireID) { 118 continue 119 } 120 if level := tree.GetWireLevel(wireID); (level + 1) > b.maxLevel { 121 b.maxLevel = level + 1 122 } 123 } 124 } 125 b.maxLevelOffset = j 126 b.maxLevelPosition = nbEntries 127 } 128 129 maxLevel := b.maxLevel - 1 // offset for default value. 130 131 // update the max level with the lookup query inputs wires 132 nbInputs := int(inst.Calldata[2]) 133 j := 3 134 for i := 0; i < nbInputs; i++ { 135 // first we have the length of the linear expression 136 n := int(inst.Calldata[j]) 137 j++ 138 for k := 0; k < n; k++ { 139 wireID := inst.Calldata[j+1] 140 j += 2 141 if !tree.HasWire(wireID) { 142 continue 143 } 144 if level := tree.GetWireLevel(wireID); level > maxLevel { 145 maxLevel = level 146 } 147 } 148 } 149 150 // finally we have the outputs 151 maxLevel++ 152 for i := 0; i < nbInputs; i++ { 153 tree.InsertWire(uint32(i+int(inst.WireOffset)), maxLevel) 154 } 155 156 return maxLevel 157 }