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  }