From 43660826fa07584fa33e167ad9f5185be54a694f Mon Sep 17 00:00:00 2001 From: siddharth0a Date: Tue, 2 Apr 2024 20:17:33 +0900 Subject: [PATCH] consensus/eccpow: add api and sealer --- consensus/eccpow/api.go | 116 +++++++ consensus/eccpow/sealer.go | 584 ++++++++++++++++++++++++++++++++ consensus/eccpow/sealer_test.go | 214 ++++++++++++ 3 files changed, 914 insertions(+) create mode 100644 consensus/eccpow/api.go create mode 100644 consensus/eccpow/sealer.go create mode 100644 consensus/eccpow/sealer_test.go diff --git a/consensus/eccpow/api.go b/consensus/eccpow/api.go new file mode 100644 index 0000000000..96520b6bf1 --- /dev/null +++ b/consensus/eccpow/api.go @@ -0,0 +1,116 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eccpow + +import ( + "errors" + + "github.com/cryptoecc/ETH-ECC/common" + "github.com/cryptoecc/ETH-ECC/common/hexutil" + "github.com/cryptoecc/ETH-ECC/core/types" +) + +var erreccStopped = errors.New("ecc stopped") + +// API exposes ecc related methods for the RPC interface. +type API struct { + ecc *ECC // Make sure the mode of ecc is normal. +} + +// GetWork returns a work package for external miner. +// +// The work package consists of 3 strings: +// result[0] - 32 bytes hex encoded current block header pow-hash +// result[1] - 32 bytes hex encoded seed hash used for DAG +// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty +// result[3] - hex encoded block number +func (api *API) GetWork() ([4]string, error) { + //if api.ecc.config.PowMode != ModeNormal && api.ecc.config.PowMode != ModeTest { + // return [4]string{}, errors.New("not supported") + //} + + var ( + workCh = make(chan [4]string, 1) + errc = make(chan error, 1) + ) + + select { + case api.ecc.fetchWorkCh <- &sealWork{errc: errc, res: workCh}: + case <-api.ecc.remote.exitCh: + return [4]string{}, erreccStopped + } + + select { + case work := <-workCh: + return work, nil + case err := <-errc: + return [4]string{}, err + } +} + +// SubmitWork can be used by external miner to submit their POW solution. +// It returns an indication if the work was accepted. +// Note either an invalid solution, a stale work a non-existent work will return false. +func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest common.Hash) bool { + //if api.ecc.config.PowMode != ModeNormal && api.ecc.config.PowMode != ModeTest { + // return false + //} + + var errc = make(chan error, 1) + + select { + case api.ecc.submitWorkCh <- &mineResult{ + nonce: nonce, + mixDigest: digest, + hash: hash, + errc: errc, + }: + case <-api.ecc.remote.exitCh: + return false + } + + err := <-errc + return err == nil +} + +// SubmitHashrate can be used for remote miners to submit their hash rate. +// This enables the node to report the combined hash rate of all miners +// which submit work through this node. +// +// It accepts the miner hash rate and an identifier which must be unique +// between nodes. +func (api *API) SubmitHashRate(rate hexutil.Uint64, id common.Hash) bool { + + var done = make(chan struct{}, 1) + + select { + case api.ecc.submitRateCh <- &hashrate{done: done, rate: uint64(rate), id: id}: + case <-api.ecc.remote.exitCh: + return false + } + + // Block until hash rate submitted successfully. + <-done + + return true +} + +// Geccrate returns the current hashrate for local CPU miner and remote miner. +func (api *API) Geccrate() uint64 { + + return uint64(api.ecc.Hashrate()) +} diff --git a/consensus/eccpow/sealer.go b/consensus/eccpow/sealer.go new file mode 100644 index 0000000000..ddbf65a442 --- /dev/null +++ b/consensus/eccpow/sealer.go @@ -0,0 +1,584 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eccpow + +import ( + "bytes" + "context" + crand "crypto/rand" + "encoding/binary" + "encoding/json" + "errors" + "math" + "math/big" + "math/rand" + "net/http" + "runtime" + "sync" + "time" + + "github.com/cryptoecc/ETH-ECC/common" + "github.com/cryptoecc/ETH-ECC/common/hexutil" + "github.com/cryptoecc/ETH-ECC/consensus" + "github.com/cryptoecc/ETH-ECC/core/types" + "github.com/cryptoecc/ETH-ECC/crypto" + "github.com/cryptoecc/ETH-ECC/log" +) + +const ( + // staleThreshold is the maximum depth of the acceptable stale but valid ecc solution. + staleThreshold = 7 +) + +var ( + errNoMiningWork = errors.New("no mining work available yet") + errInvalidSealResult = errors.New("invalid or stale proof-of-work solution") +) + +// Seal implements consensus.Engine, attempting to find a nonce that satisfies +// the block's difficulty requirements. +func (ecc *ECC) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + // If we're running a fake PoW, simply return a 0 nonce immediately + if ecc.config.PowMode == ModeFake || ecc.config.PowMode == ModeFullFake { + header := block.Header() + header.Nonce, header.MixDigest = types.BlockNonce{}, common.Hash{} + select { + case results <- block.WithSeal(header): + default: + log.Warn("Sealing result is not read by miner", "mode", "fake", "sealhash", ecc.SealHash(block.Header())) + } + return nil + } + // If we're running a shared PoW, delegate sealing to it + if ecc.shared != nil { + return ecc.shared.Seal(chain, block, results, stop) + } + // Create a runner and the multiple search threads it directs + abort := make(chan struct{}) + + ecc.lock.Lock() + threads := ecc.threads + if ecc.rand == nil { + seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + ecc.lock.Unlock() + return err + } + ecc.rand = rand.New(rand.NewSource(seed.Int64())) + } + ecc.lock.Unlock() + if threads == 0 { + threads = runtime.NumCPU() + } + if threads < 0 { + threads = 0 // Allows disabling local mining without extra logic around local/remote + } + // Push new work to remote sealer + if ecc.remote != nil { + ecc.remote.workCh <- &sealTask{block: block, results: results} + } + var ( + pend sync.WaitGroup + locals = make(chan *types.Block) + ) + + for i := 0; i < threads; i++ { + pend.Add(1) + go func(id int, nonce uint64) { + defer pend.Done() + //ecc.mine(block, id, nonce, abort, locals) + if chain.Config().IsSeoul(block.Header().Number){ + ecc.mine_seoul(block, id, nonce, abort, locals) + } else{ + ecc.mine(block, id, nonce, abort, locals) + } + }(i, uint64(ecc.rand.Int63())) + } + + // Wait until sealing is terminated or a nonce is found + go func() { + var result *types.Block + select { + case <-stop: + // Outside abort, stop all miner threads + close(abort) + case result = <-locals: + // One of the threads found a block, abort all others + select { + case results <- result: + default: + ecc.config.Log.Warn("Sealing result is not read by miner", "mode", "local", "sealhash", ecc.SealHash(block.Header())) + } + close(abort) + case <-ecc.update: + // Thread count was changed on user request, restart + close(abort) + if err := ecc.Seal(chain, block, results, stop); err != nil { + ecc.config.Log.Error("Failed to restart sealing after update", "err", err) + } + } + // Wait for all miners to terminate and return the block + pend.Wait() + }() + + return nil +} + +// mine is the actual proof-of-work miner that searches for a nonce starting from +// seed that results in correct final block difficulty. +func (ecc *ECC) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { + // Extract some data from the header + var ( + header = block.Header() + hash = ecc.SealHash(header).Bytes() + ) + // Start generating random nonces until we abort or find a good one + var ( + total_attempts = int64(0) + attempts = int64(0) + nonce = seed + ) + logger := log.New("miner", id) + logger.Trace("Started ecc search for new nonces", "seed", seed) +search: + for { + select { + case <-abort: + // Mining terminated, update stats and abort + logger.Trace("ecc nonce search aborted", "attempts", nonce-seed) + ecc.hashrate.Mark(attempts) + break search + + default: + // We don't have to update hash rate on every nonce, so update after after 2^X nonces + total_attempts = total_attempts + 64 + attempts = attempts + 64 + if (attempts % (1 << 15)) == 0 { + ecc.hashrate.Mark(attempts) + attempts = 0 + } + // Compute the PoW value of this nonce + + flag, _, outputWord, LDPCNonce, digest := RunOptimizedConcurrencyLDPC(header, hash) + + // Correct nonce found, create a new header with it + if flag == true { + //level := SearchLevel_Seoul(header.Difficulty) + //fmt.Printf("level: %v\n", level) + //fmt.Printf("total attempts: %v\n", total_attempts) + //fmt.Printf("hashrate: %v\n", ecc.Hashrate()) + //fmt.Printf("Codeword found with nonce = %d\n", LDPCNonce) + //fmt.Printf("Codeword : %d\n", outputWord) + + header = types.CopyHeader(header) + header.MixDigest = common.BytesToHash(digest) + header.Nonce = types.EncodeNonce(LDPCNonce) + + //convert codeword + var codeword []byte + var codeVal byte + for i, v := range outputWord { + codeVal |= byte(v) << (7 - i%8) + if i%8 == 7 { + codeword = append(codeword, codeVal) + codeVal = 0 + } + } + if len(outputWord)%8 != 0 { + codeword = append(codeword, codeVal) + } + header.Codeword = make([]byte, len(codeword)) + copy(header.Codeword, codeword) + //fmt.Printf("header: %v\n", header) + //fmt.Printf("header Codeword : %v\n", header.Codeword) + + // Seal and return a block (if still needed) + select { + case found <- block.WithSeal(header): + logger.Trace("ecc nonce found and reported", "LDPCNonce", LDPCNonce) + case <-abort: + logger.Trace("ecc nonce found but discarded", "LDPCNonce", LDPCNonce) + } + break search + } + } + } +} + +func (ecc *ECC) mine_seoul(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { + // Extract some data from the header + var ( + header = block.Header() + hash = ecc.SealHash(header).Bytes() + ) + // Start generating random nonces until we abort or find a good one + var ( + total_attempts = int64(0) + attempts = int64(0) + nonce = seed + ) + logger := log.New("miner", id) + logger.Trace("Started ecc search for new nonces", "seed", seed) + + parameters, _ := setParameters_Seoul(header) + //fmt.Println(parameters) + H := generateH(parameters) + colInRow, rowInCol := generateQ(parameters, H) + +search: + for { + select { + case <-abort: + // Mining terminated, update stats and abort + logger.Trace("ecc nonce search aborted", "attempts", nonce-seed) + ecc.hashrate.Mark(attempts) + break search + + default: + // We don't have to update hash rate on every nonce, so update after after 2^X nonces + total_attempts = total_attempts + 1 + attempts = attempts + 1 + if (attempts % (1 << 15)) == 0 { + ecc.hashrate.Mark(attempts) + attempts = 0 + } + + digest := make([]byte, 40) + copy(digest, hash) + binary.LittleEndian.PutUint64(digest[32:], nonce) + digest = crypto.Keccak512(digest) + //fmt.Printf("nonce: %v\n", digest) + + goRoutineHashVector := generateHv(parameters, digest) + goRoutineHashVector, goRoutineOutputWord, _ := OptimizedDecodingSeoul(parameters, goRoutineHashVector, H, rowInCol, colInRow) + + flag, _ := MakeDecision_Seoul(header, colInRow, goRoutineOutputWord) + //fmt.Printf("nonce: %v\n", nonce) + //fmt.Printf("nonce: %v\n", weight) + + if flag == true { + //hashVector := goRoutineHashVector + outputWord := goRoutineOutputWord + + //level := SearchLevel_Seoul(header.Difficulty) + /*fmt.Printf("level: %v\n", level) + fmt.Printf("total attempts: %v\n", total_attempts) + fmt.Printf("hashrate: %v\n", ecc.Hashrate()) + fmt.Printf("Codeword found with nonce = %d\n", nonce) + fmt.Printf("Codeword : %d\n", outputWord)*/ + + header = types.CopyHeader(header) + header.CodeLength = uint64(parameters.n) + header.MixDigest = common.BytesToHash(digest) + header.Nonce = types.EncodeNonce(nonce) + + //convert codeword + var codeword []byte + var codeVal byte + for i, v := range outputWord { + codeVal |= byte(v) << (7 - i%8) + if i%8 == 7 { + codeword = append(codeword, codeVal) + codeVal = 0 + } + } + if len(outputWord)%8 != 0 { + codeword = append(codeword, codeVal) + } + header.Codeword = make([]byte, len(codeword)) + copy(header.Codeword, codeword) + //fmt.Printf("header: %v\n", header) + //fmt.Printf("header Codeword : %v\n", header.Codeword) + + // Seal and return a block (if still needed) + select { + case found <- block.WithSeal(header): + logger.Trace("ecc nonce found and reported", "LDPCNonce", nonce) + case <-abort: + logger.Trace("ecc nonce found but discarded", "LDPCNonce", nonce) + } + break search + } + nonce++ + } + } +} + + +//GPU MINING... NEED TO UPDTAE +// This is the timeout for HTTP requests to notify external miners. +const remoteSealerTimeout = 1 * time.Second + +type remoteSealer struct { + works map[common.Hash]*types.Block + rates map[common.Hash]hashrate + currentBlock *types.Block + currentWork [4]string + notifyCtx context.Context + cancelNotify context.CancelFunc // cancels all notification requests + reqWG sync.WaitGroup // tracks notification request goroutines + + ecc *ECC + noverify bool + notifyURLs []string + results chan<- *types.Block + workCh chan *sealTask // Notification channel to push new work and relative result channel to remote sealer + fetchWorkCh chan *sealWork // Channel used for remote sealer to fetch mining work + submitWorkCh chan *mineResult // Channel used for remote sealer to submit their mining result + fetchRateCh chan chan uint64 // Channel used to gather submitted hash rate for local or remote sealer. + submitRateCh chan *hashrate // Channel used for remote sealer to submit their mining hashrate + requestExit chan struct{} + exitCh chan struct{} +} + +// sealTask wraps a seal block with relative result channel for remote sealer thread. +type sealTask struct { + block *types.Block + results chan<- *types.Block +} + +// mineResult wraps the pow solution parameters for the specified block. +type mineResult struct { + nonce types.BlockNonce + mixDigest common.Hash + hash common.Hash + + errc chan error +} + +// hashrate wraps the hash rate submitted by the remote sealer. +type hashrate struct { + id common.Hash + ping time.Time + rate uint64 + + done chan struct{} +} + +// sealWork wraps a seal work package for remote sealer. +type sealWork struct { + errc chan error + res chan [4]string +} + +func startRemoteSealer(ecc *ECC, urls []string, noverify bool) *remoteSealer { + ctx, cancel := context.WithCancel(context.Background()) + s := &remoteSealer{ + ecc: ecc, + noverify: noverify, + notifyURLs: urls, + notifyCtx: ctx, + cancelNotify: cancel, + works: make(map[common.Hash]*types.Block), + rates: make(map[common.Hash]hashrate), + workCh: make(chan *sealTask), + fetchWorkCh: make(chan *sealWork), + submitWorkCh: make(chan *mineResult), + fetchRateCh: make(chan chan uint64), + submitRateCh: make(chan *hashrate), + requestExit: make(chan struct{}), + exitCh: make(chan struct{}), + } + go s.loop() + return s +} + +func (s *remoteSealer) loop() { + defer func() { + s.ecc.config.Log.Trace("ECC remote sealer is exiting") + s.cancelNotify() + s.reqWG.Wait() + close(s.exitCh) + }() + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case work := <-s.workCh: + // Update current work with new received block. + // Note same work can be past twice, happens when changing CPU threads. + s.results = work.results + s.makeWork(work.block) + s.notifyWork() + + case work := <-s.fetchWorkCh: + // Return current mining work to remote miner. + if s.currentBlock == nil { + work.errc <- errNoMiningWork + } else { + work.res <- s.currentWork + } + + case result := <-s.submitWorkCh: + // Verify submitted PoW solution based on maintained mining blocks. + if s.submitWork(result.nonce, result.mixDigest, result.hash) { + result.errc <- nil + } else { + result.errc <- errInvalidSealResult + } + + case result := <-s.submitRateCh: + // Trace remote sealer's hash rate by submitted value. + s.rates[result.id] = hashrate{rate: result.rate, ping: time.Now()} + close(result.done) + + case req := <-s.fetchRateCh: + // Gather all hash rate submitted by remote sealer. + var total uint64 + for _, rate := range s.rates { + // this could overflow + total += rate.rate + } + req <- total + + case <-ticker.C: + // Clear stale submitted hash rate. + for id, rate := range s.rates { + if time.Since(rate.ping) > 10*time.Second { + delete(s.rates, id) + } + } + // Clear stale pending blocks + if s.currentBlock != nil { + for hash, block := range s.works { + if block.NumberU64()+staleThreshold <= s.currentBlock.NumberU64() { + delete(s.works, hash) + } + } + } + + case <-s.requestExit: + return + } + } +} + +// makeWork creates a work package for external miner. +// +// The work package consists of 3 strings: +// result[0], 32 bytes hex encoded current block header pow-hash +// result[1], 32 bytes hex encoded seed hash used for DAG +// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty +// result[3], hex encoded block number +func (s *remoteSealer) makeWork(block *types.Block) { + hash := s.ecc.SealHash(block.Header()) + s.currentWork[0] = hash.Hex() + s.currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex() + s.currentWork[2] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex() + s.currentWork[3] = hexutil.EncodeBig(block.Number()) + + // Trace the seal work fetched by remote sealer. + s.currentBlock = block + s.works[hash] = block +} + +// notifyWork notifies all the specified mining endpoints of the availability of +// new work to be processed. +func (s *remoteSealer) notifyWork() { + work := s.currentWork + + // Encode the JSON payload of the notification. When NotifyFull is set, + // this is the complete block header, otherwise it is a JSON array. + var blob []byte + if s.ecc.config.NotifyFull { + blob, _ = json.Marshal(s.currentBlock.Header()) + } else { + blob, _ = json.Marshal(work) + } + + s.reqWG.Add(len(s.notifyURLs)) + for _, url := range s.notifyURLs { + go s.sendNotification(s.notifyCtx, url, blob, work) + } +} + +func (s *remoteSealer) sendNotification(ctx context.Context, url string, json []byte, work [4]string) { + defer s.reqWG.Done() + + req, err := http.NewRequest("POST", url, bytes.NewReader(json)) + if err != nil { + s.ecc.config.Log.Warn("Can't create remote miner notification", "err", err) + return + } + ctx, cancel := context.WithTimeout(ctx, remoteSealerTimeout) + defer cancel() + req = req.WithContext(ctx) + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + s.ecc.config.Log.Warn("Failed to notify remote miner", "err", err) + } else { + s.ecc.config.Log.Trace("Notified remote miner", "miner", url, "hash", work[0], "target", work[2]) + resp.Body.Close() + } +} + +// submitWork verifies the submitted pow solution, returning +// whether the solution was accepted or not (not can be both a bad pow as well as +// any other error, like no pending work or stale mining result). +func (s *remoteSealer) submitWork(nonce types.BlockNonce, mixDigest common.Hash, sealhash common.Hash) bool { + if s.currentBlock == nil { + s.ecc.config.Log.Error("Pending work without block", "sealhash", sealhash) + return false + } + // Make sure the work submitted is present + block := s.works[sealhash] + if block == nil { + s.ecc.config.Log.Warn("Work submitted but none pending", "sealhash", sealhash, "curnumber", s.currentBlock.NumberU64()) + return false + } + // Verify the correctness of submitted result. + header := block.Header() + header.Nonce = nonce + header.MixDigest = mixDigest + + start := time.Now() + if !s.noverify { + if err := s.ecc.verifySeal(nil, header); err != nil { + s.ecc.config.Log.Warn("Invalid proof-of-work submitted", "sealhash", sealhash, "elapsed", common.PrettyDuration(time.Since(start)), "err", err) + return false + } + } + // Make sure the result channel is assigned. + if s.results == nil { + s.ecc.config.Log.Warn("Eccresult channel is empty, submitted mining result is rejected") + return false + } + s.ecc.config.Log.Trace("Verified correct proof-of-work", "sealhash", sealhash, "elapsed", common.PrettyDuration(time.Since(start))) + + // Solutions seems to be valid, return to the miner and notify acceptance. + solution := block.WithSeal(header) + + // The submitted solution is within the scope of acceptance. + if solution.NumberU64()+staleThreshold > s.currentBlock.NumberU64() { + select { + case s.results <- solution: + s.ecc.config.Log.Debug("Work submitted is acceptable", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) + return true + default: + s.ecc.config.Log.Warn("Sealing result is not read by miner", "mode", "remote", "sealhash", sealhash) + return false + } + } + // The submitted block is too old to accept, drop it. + s.ecc.config.Log.Warn("Work submitted is too old", "number", solution.NumberU64(), "sealhash", sealhash, "hash", solution.Hash()) + return false +} + diff --git a/consensus/eccpow/sealer_test.go b/consensus/eccpow/sealer_test.go new file mode 100644 index 0000000000..e7c0311c58 --- /dev/null +++ b/consensus/eccpow/sealer_test.go @@ -0,0 +1,214 @@ +package eccpow + +import ( + "encoding/json" + "io/ioutil" + "math/big" + "net" + "net/http" + "testing" + "time" + + "github.com/cryptoecc/ETH-ECC/common" + "github.com/cryptoecc/ETH-ECC/core/types" +) + +// Tests whether remote HTTP servers are correctly notified of new work. +func TestRemoteNotify(t *testing.T) { + // Start a simple webserver to capture notifications + sink := make(chan [3]string) + + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + blob, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatalf("failed to read miner notification: %v", err) + } + var work [3]string + if err := json.Unmarshal(blob, &work); err != nil { + t.Fatalf("failed to unmarshal miner notification: %v", err) + } + sink <- work + }), + } + // Open a custom listener to extract its local address + listener, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to open notification server: %v", err) + } + defer listener.Close() + + go server.Serve(listener) + + // Wait for server to start listening + var tries int + for tries = 0; tries < 10; tries++ { + conn, _ := net.DialTimeout("tcp", listener.Addr().String(), 1*time.Second) + if conn != nil { + break + } + } + if tries == 10 { + t.Fatal("tcp listener not ready for more than 10 seconds") + } + + // Create the custom ecc engine + ecc := NewTester([]string{"http://" + listener.Addr().String()}, false) + defer ecc.Close() + + // Stream a work task and ensure the notification bubbles out + header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)} + block := types.NewBlockWithHeader(header) + + ecc.Seal(nil, block, nil, nil) + select { + case work := <-sink: + if want := ecc.SealHash(header).Hex(); work[0] != want { + t.Errorf("work packet hash mismatch: have %s, want %s", work[0], want) + } + //if want := common.BytesToHash(SeedHash(header.Number.Uint64())).Hex(); work[1] != want { + if want := header.ParentHash.Hex(); work[1] != want { + t.Errorf("work packet seed mismatch: have %s, want %s", work[1], want) + } + //target := new(big.Int).Div(new(big.Int).Lsh(big.NewInt(1), 256), header.Difficulty) + //if want := common.BytesToHash(target.Bytes()).Hex(); work[2] != want { + // t.Errorf("work packet target mismatch: have %s, want %s", work[2], want) + //} + case <-time.After(3 * time.Second): + t.Fatalf("notification timed out") + } +} + +// Tests that pushing work packages fast to the miner doesn't cause any data race +// issues in the notifications. +func TestRemoteMultiNotify(t *testing.T) { + // Start a simple webserver to capture notifications + sink := make(chan [3]string, 64) + + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + blob, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatalf("failed to read miner notification: %v", err) + } + var work [3]string + if err := json.Unmarshal(blob, &work); err != nil { + t.Fatalf("failed to unmarshal miner notification: %v", err) + } + sink <- work + }), + } + // Open a custom listener to extract its local address + listener, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatalf("failed to open notification server: %v", err) + } + defer listener.Close() + + go server.Serve(listener) + + // Create the custom ecc engine + ecc := NewTester([]string{"http://" + listener.Addr().String()}, false) + defer ecc.Close() + + // Stream a lot of work task and ensure all the notifications bubble out + for i := 0; i < cap(sink); i++ { + header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)} + block := types.NewBlockWithHeader(header) + + ecc.Seal(nil, block, nil, nil) + } + /* + for i := 0; i < cap(sink); i++ { + select { + case <-sink: + case <-time.After(3 * time.Second): + t.Fatalf("notification %d timed out", i) + } + } + */ +} + +// Tests whether stale solutions are correctly processed. +func TestStaleSubmission(t *testing.T) { + ecc := NewTester(nil, true) + defer ecc.Close() + api := &API{ecc} + + fakeNonce, fakeDigest := types.BlockNonce{0x01, 0x02, 0x03}, common.HexToHash("deadbeef") + + testcases := []struct { + headers []*types.Header + submitIndex int + submitRes bool + }{ + // Case1: submit solution for the latest mining package + { + []*types.Header{ + {ParentHash: common.BytesToHash([]byte{0xa}), Number: big.NewInt(1), Difficulty: big.NewInt(100000000)}, + }, + 0, + true, + }, + // Case2: submit solution for the previous package but have same parent. + { + []*types.Header{ + {ParentHash: common.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000000)}, + {ParentHash: common.BytesToHash([]byte{0xb}), Number: big.NewInt(2), Difficulty: big.NewInt(100000001)}, + }, + 0, + true, + }, + // Case3: submit stale but acceptable solution + { + []*types.Header{ + {ParentHash: common.BytesToHash([]byte{0xc}), Number: big.NewInt(3), Difficulty: big.NewInt(100000000)}, + {ParentHash: common.BytesToHash([]byte{0xd}), Number: big.NewInt(9), Difficulty: big.NewInt(100000000)}, + }, + 0, + true, + }, + // Case4: submit very old solution + { + []*types.Header{ + {ParentHash: common.BytesToHash([]byte{0xe}), Number: big.NewInt(10), Difficulty: big.NewInt(100000000)}, + {ParentHash: common.BytesToHash([]byte{0xf}), Number: big.NewInt(17), Difficulty: big.NewInt(100000000)}, + }, + 0, + false, + }, + } + results := make(chan *types.Block, 16) + + for id, c := range testcases { + for _, h := range c.headers { + ecc.Seal(nil, types.NewBlockWithHeader(h), results, nil) + } + if res := api.SubmitWork(fakeNonce, ecc.SealHash(c.headers[c.submitIndex]), fakeDigest); res != c.submitRes { + t.Errorf("case %d submit result mismatch, want %t, get %t", id+1, c.submitRes, res) + } + if !c.submitRes { + continue + } + select { + case res := <-results: + if res.Header().Nonce != fakeNonce { + t.Errorf("case %d block nonce mismatch, want %s, get %s", id+1, fakeNonce, res.Header().Nonce) + } + if res.Header().MixDigest != fakeDigest { + t.Errorf("case %d block digest mismatch, want %s, get %s", id+1, fakeDigest, res.Header().MixDigest) + } + if res.Header().Difficulty.Uint64() != c.headers[c.submitIndex].Difficulty.Uint64() { + t.Errorf("case %d block difficulty mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Difficulty, res.Header().Difficulty) + } + if res.Header().Number.Uint64() != c.headers[c.submitIndex].Number.Uint64() { + t.Errorf("case %d block number mismatch, want %d, get %d", id+1, c.headers[c.submitIndex].Number.Uint64(), res.Header().Number.Uint64()) + } + if res.Header().ParentHash != c.headers[c.submitIndex].ParentHash { + t.Errorf("case %d block parent hash mismatch, want %s, get %s", id+1, c.headers[c.submitIndex].ParentHash.Hex(), res.Header().ParentHash.Hex()) + } + case <-time.NewTimer(time.Second).C: + t.Errorf("case %d fetch ecc result timeout", id+1) + } + } +}