mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 00:15:28 +08:00
289 lines
6.4 KiB
Go
289 lines
6.4 KiB
Go
package deepseek
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"errors"
|
|
"math"
|
|
"os"
|
|
stdruntime "runtime"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"ds2api/internal/config"
|
|
|
|
"github.com/tetratelabs/wazero"
|
|
"github.com/tetratelabs/wazero/api"
|
|
)
|
|
|
|
type PowSolver struct {
|
|
wasmPath string
|
|
once sync.Once
|
|
err error
|
|
|
|
runtime wazero.Runtime
|
|
compiled wazero.CompiledModule
|
|
pool chan *pooledModule
|
|
poolSize int
|
|
}
|
|
|
|
type pooledModule struct {
|
|
mod api.Module
|
|
stackFn api.Function
|
|
allocFn api.Function
|
|
freeFn api.Function
|
|
solveFn api.Function
|
|
}
|
|
|
|
func NewPowSolver(wasmPath string) *PowSolver {
|
|
return &PowSolver{wasmPath: wasmPath}
|
|
}
|
|
|
|
func (p *PowSolver) init(ctx context.Context) error {
|
|
p.once.Do(func() {
|
|
wasmBytes, err := os.ReadFile(p.wasmPath)
|
|
if err != nil {
|
|
if len(embeddedWASM) == 0 {
|
|
p.err = err
|
|
return
|
|
}
|
|
wasmBytes = embeddedWASM
|
|
}
|
|
p.runtime = wazero.NewRuntime(ctx)
|
|
p.compiled, p.err = p.runtime.CompileModule(ctx, wasmBytes)
|
|
if p.err == nil {
|
|
p.poolSize = powPoolSizeFromEnv()
|
|
p.pool = make(chan *pooledModule, p.poolSize)
|
|
for range p.poolSize {
|
|
inst, err := p.createModule(ctx)
|
|
if err != nil {
|
|
p.err = err
|
|
return
|
|
}
|
|
p.pool <- inst
|
|
}
|
|
}
|
|
})
|
|
return p.err
|
|
}
|
|
|
|
func (p *PowSolver) Compute(ctx context.Context, challenge map[string]any) (int64, error) {
|
|
if err := p.init(ctx); err != nil {
|
|
return 0, err
|
|
}
|
|
algo, _ := challenge["algorithm"].(string)
|
|
if algo != "DeepSeekHashV1" {
|
|
return 0, errors.New("unsupported algorithm")
|
|
}
|
|
challengeStr, _ := challenge["challenge"].(string)
|
|
salt, _ := challenge["salt"].(string)
|
|
signature, _ := challenge["signature"].(string)
|
|
targetPath, _ := challenge["target_path"].(string)
|
|
_ = signature
|
|
_ = targetPath
|
|
|
|
difficulty := toFloat64(challenge["difficulty"], 144000)
|
|
expireAt := toInt64(challenge["expire_at"], 1680000000)
|
|
prefix := salt + "_" + itoa(expireAt) + "_"
|
|
|
|
pm, err := p.acquireModule(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer p.releaseModule(pm)
|
|
|
|
mem := pm.mod.Memory()
|
|
if mem == nil {
|
|
return 0, errors.New("wasm memory missing")
|
|
}
|
|
retPtrs, err := pm.stackFn.Call(ctx, uint64(uint32(^uint32(15)))) // -16 i32
|
|
if err != nil || len(retPtrs) == 0 {
|
|
return 0, errors.New("stack alloc failed")
|
|
}
|
|
retptr := uint32(retPtrs[0])
|
|
defer func() {
|
|
_, _ = pm.stackFn.Call(context.Background(), 16)
|
|
}()
|
|
|
|
chPtr, chLen, err := writeUTF8(ctx, pm.allocFn, mem, challengeStr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer freeUTF8(pm.freeFn, chPtr, chLen)
|
|
|
|
prefixPtr, prefixLen, err := writeUTF8(ctx, pm.allocFn, mem, prefix)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer freeUTF8(pm.freeFn, prefixPtr, prefixLen)
|
|
|
|
if _, err := pm.solveFn.Call(ctx,
|
|
uint64(retptr),
|
|
uint64(chPtr), uint64(chLen),
|
|
uint64(prefixPtr), uint64(prefixLen),
|
|
math.Float64bits(difficulty),
|
|
); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
statusBytes, ok := mem.Read(retptr, 4)
|
|
if !ok {
|
|
return 0, errors.New("read status failed")
|
|
}
|
|
status := int32(binary.LittleEndian.Uint32(statusBytes))
|
|
valueBytes, ok := mem.Read(retptr+8, 8)
|
|
if !ok {
|
|
return 0, errors.New("read value failed")
|
|
}
|
|
value := math.Float64frombits(binary.LittleEndian.Uint64(valueBytes))
|
|
if status == 0 {
|
|
return 0, errors.New("pow solve failed")
|
|
}
|
|
return int64(value), nil
|
|
}
|
|
|
|
func (p *PowSolver) createModule(ctx context.Context) (*pooledModule, error) {
|
|
mod, err := p.runtime.InstantiateModule(ctx, p.compiled, wazero.NewModuleConfig())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stackFn := mod.ExportedFunction("__wbindgen_add_to_stack_pointer")
|
|
allocFn := mod.ExportedFunction("__wbindgen_export_0")
|
|
solveFn := mod.ExportedFunction("wasm_solve")
|
|
if stackFn == nil || allocFn == nil || solveFn == nil {
|
|
_ = mod.Close(context.Background())
|
|
return nil, errors.New("required wasm exports missing")
|
|
}
|
|
return &pooledModule{
|
|
mod: mod,
|
|
stackFn: stackFn,
|
|
allocFn: allocFn,
|
|
freeFn: mod.ExportedFunction("__wbindgen_export_2"),
|
|
solveFn: solveFn,
|
|
}, nil
|
|
}
|
|
|
|
func (p *PowSolver) acquireModule(ctx context.Context) (*pooledModule, error) {
|
|
if p.pool != nil {
|
|
for {
|
|
select {
|
|
case pm := <-p.pool:
|
|
if pm != nil {
|
|
return pm, nil
|
|
}
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
}
|
|
return p.createModule(ctx)
|
|
}
|
|
|
|
func (p *PowSolver) releaseModule(pm *pooledModule) {
|
|
if pm == nil || pm.mod == nil {
|
|
return
|
|
}
|
|
if p.pool != nil {
|
|
select {
|
|
case p.pool <- pm:
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
_ = pm.mod.Close(context.Background())
|
|
}
|
|
|
|
func writeUTF8(ctx context.Context, allocFn api.Function, mem api.Memory, text string) (uint32, uint32, error) {
|
|
data := []byte(text)
|
|
res, err := allocFn.Call(ctx, uint64(len(data)), 1)
|
|
if err != nil || len(res) == 0 {
|
|
return 0, 0, errors.New("alloc failed")
|
|
}
|
|
ptr := uint32(res[0])
|
|
if !mem.Write(ptr, data) {
|
|
return 0, 0, errors.New("mem write failed")
|
|
}
|
|
return ptr, uint32(len(data)), nil
|
|
}
|
|
|
|
func freeUTF8(freeFn api.Function, ptr, size uint32) {
|
|
if freeFn == nil || ptr == 0 || size == 0 {
|
|
return
|
|
}
|
|
_, _ = freeFn.Call(context.Background(), uint64(ptr), uint64(size), 1)
|
|
}
|
|
|
|
func BuildPowHeader(challenge map[string]any, answer int64) (string, error) {
|
|
payload := map[string]any{
|
|
"algorithm": challenge["algorithm"],
|
|
"challenge": challenge["challenge"],
|
|
"salt": challenge["salt"],
|
|
"answer": answer,
|
|
"signature": challenge["signature"],
|
|
"target_path": challenge["target_path"],
|
|
}
|
|
b, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return base64.StdEncoding.EncodeToString(b), nil
|
|
}
|
|
|
|
func toFloat64(v any, d float64) float64 {
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return n
|
|
case int:
|
|
return float64(n)
|
|
case int64:
|
|
return float64(n)
|
|
default:
|
|
return d
|
|
}
|
|
}
|
|
|
|
func toInt64(v any, d int64) int64 {
|
|
switch n := v.(type) {
|
|
case float64:
|
|
return int64(n)
|
|
case int:
|
|
return int64(n)
|
|
case int64:
|
|
return n
|
|
default:
|
|
return d
|
|
}
|
|
}
|
|
|
|
func itoa(n int64) string {
|
|
return strconv.FormatInt(n, 10)
|
|
}
|
|
|
|
func powPoolSizeFromEnv() int {
|
|
const fallback = 4
|
|
n := fallback
|
|
if cpus := stdruntime.GOMAXPROCS(0); cpus > 0 {
|
|
n = cpus
|
|
}
|
|
if raw := os.Getenv("DS2API_POW_POOL_SIZE"); raw != "" {
|
|
if v, err := strconv.Atoi(raw); err == nil && v > 0 {
|
|
n = v
|
|
}
|
|
}
|
|
if n > 64 {
|
|
return 64
|
|
}
|
|
return n
|
|
}
|
|
|
|
func PreloadWASM(wasmPath string) {
|
|
solver := NewPowSolver(wasmPath)
|
|
if err := solver.init(context.Background()); err != nil {
|
|
config.Logger.Warn("[WASM] preload failed", "error", err)
|
|
return
|
|
}
|
|
config.Logger.Info("[WASM] module preloaded", "path", wasmPath)
|
|
}
|