git.gammaspectra.live/P2Pool/consensus@v0.0.0-20240403173234-a039820b20c9/monero/randomx/randomx_cgo.go (about) 1 //go:build cgo && !disable_randomx_library 2 3 package randomx 4 5 import ( 6 "bytes" 7 "crypto/subtle" 8 "encoding/hex" 9 "errors" 10 "git.gammaspectra.live/P2Pool/consensus/monero/crypto" 11 "git.gammaspectra.live/P2Pool/consensus/types" 12 "git.gammaspectra.live/P2Pool/consensus/utils" 13 "git.gammaspectra.live/P2Pool/randomx-go-bindings" 14 "runtime" 15 "slices" 16 "sync" 17 "unsafe" 18 ) 19 20 type hasherCollection struct { 21 lock sync.RWMutex 22 index int 23 flags []Flag 24 cache []*hasherState 25 } 26 27 func (h *hasherCollection) Hash(key []byte, input []byte) (types.Hash, error) { 28 if hash, err := func() (types.Hash, error) { 29 h.lock.RLock() 30 defer h.lock.RUnlock() 31 for _, c := range h.cache { 32 if len(c.key) > 0 && bytes.Compare(c.key, key) == 0 { 33 return c.Hash(input), nil 34 } 35 } 36 37 return types.ZeroHash, errors.New("no hasher") 38 }(); err == nil && hash != types.ZeroHash { 39 return hash, nil 40 } else { 41 h.lock.Lock() 42 defer h.lock.Unlock() 43 index := h.index 44 h.index = (h.index + 1) % len(h.cache) 45 if err = h.cache[index].Init(key); err != nil { 46 return types.ZeroHash, err 47 } 48 return h.cache[index].Hash(input), nil 49 } 50 } 51 52 func (h *hasherCollection) initStates(size int) (err error) { 53 for _, c := range h.cache { 54 c.Close() 55 } 56 h.cache = make([]*hasherState, size) 57 for i := range h.cache { 58 if h.cache[i], err = newRandomXState(h.flags...); err != nil { 59 return err 60 } 61 } 62 return nil 63 } 64 65 func (h *hasherCollection) OptionFlags(flags ...Flag) error { 66 h.lock.Lock() 67 defer h.lock.Unlock() 68 if slices.Compare(h.flags, flags) != 0 { 69 h.flags = flags 70 return h.initStates(len(h.cache)) 71 } 72 return nil 73 } 74 func (h *hasherCollection) OptionNumberOfCachedStates(n int) error { 75 h.lock.Lock() 76 defer h.lock.Unlock() 77 if len(h.cache) != n { 78 return h.initStates(n) 79 } 80 return nil 81 } 82 83 func (h *hasherCollection) Close() { 84 h.lock.Lock() 85 defer h.lock.Unlock() 86 for _, c := range h.cache { 87 c.Close() 88 } 89 } 90 91 type hasherState struct { 92 lock sync.Mutex 93 dataset *randomx.RxDataset 94 vm *randomx.RxVM 95 flags randomx.Flag 96 key []byte 97 } 98 99 func ConsensusHash(buf []byte) types.Hash { 100 cache, err := randomx.AllocCache(randomx.GetFlags()) 101 if err != nil { 102 return types.ZeroHash 103 } 104 defer randomx.ReleaseCache(cache) 105 106 randomx.InitCache(cache, buf) 107 // Intentionally not a power of 2 108 const ScratchpadSize = 1009 109 110 const RandomxArgonMemory = 262144 111 n := RandomxArgonMemory * 1024 112 113 const Vec128Size = 128 / 8 114 115 type Vec128 [Vec128Size]byte 116 scratchpad := unsafe.Slice((*byte)(randomx.GetCacheMemory(cache)), n) 117 118 cachePtr := scratchpad[ScratchpadSize*Vec128Size:] 119 scratchpadTopPtr := scratchpad[:ScratchpadSize*Vec128Size] 120 for i := ScratchpadSize * Vec128Size; i < n; i += ScratchpadSize * Vec128Size { 121 stride := ScratchpadSize * Vec128Size 122 if stride > len(cachePtr) { 123 stride = len(cachePtr) 124 } 125 subtle.XORBytes(scratchpadTopPtr, scratchpadTopPtr, cachePtr[:stride]) 126 cachePtr = cachePtr[stride:] 127 } 128 129 return crypto.Keccak256(scratchpadTopPtr) 130 } 131 132 func NewRandomX(n int, flags ...Flag) (Hasher, error) { 133 collection := &hasherCollection{ 134 flags: flags, 135 } 136 137 if err := collection.initStates(n); err != nil { 138 return nil, err 139 } 140 return collection, nil 141 } 142 143 func newRandomXState(flags ...Flag) (*hasherState, error) { 144 145 applyFlags := randomx.GetFlags() 146 for _, f := range flags { 147 if f == FlagLargePages { 148 applyFlags |= randomx.FlagLargePages 149 } else if f == FlagFullMemory { 150 applyFlags |= randomx.FlagFullMEM 151 } else if f == FlagSecure { 152 applyFlags |= randomx.FlagSecure 153 } 154 } 155 h := &hasherState{ 156 flags: applyFlags, 157 } 158 if dataset, err := randomx.NewRxDataset(h.flags); err != nil { 159 return nil, err 160 } else { 161 h.dataset = dataset 162 } 163 164 return h, nil 165 } 166 167 func (h *hasherState) Init(key []byte) (err error) { 168 h.lock.Lock() 169 defer h.lock.Unlock() 170 h.key = make([]byte, len(key)) 171 copy(h.key, key) 172 173 utils.Logf("RandomX", "Initializing to seed %s", hex.EncodeToString(h.key)) 174 if h.dataset.GoInit(h.key, uint32(runtime.NumCPU())) == false { 175 return errors.New("could not initialize dataset") 176 } 177 if h.vm != nil { 178 h.vm.Close() 179 } 180 181 if h.vm, err = randomx.NewRxVM(h.dataset, h.flags); err != nil { 182 return err 183 } 184 185 utils.Logf("RandomX", "Initialized to seed %s", hex.EncodeToString(h.key)) 186 187 return nil 188 } 189 190 func (h *hasherState) Hash(input []byte) (output types.Hash) { 191 h.lock.Lock() 192 defer h.lock.Unlock() 193 outputBuf := h.vm.CalcHash(input) 194 copy(output[:], outputBuf) 195 runtime.KeepAlive(input) 196 return 197 } 198 199 func (h *hasherState) Close() { 200 h.lock.Lock() 201 defer h.lock.Unlock() 202 if h.vm != nil { 203 h.vm.Close() 204 } 205 h.dataset.Close() 206 }