From 99d71b5fb45cebd45c851bb9913ebf42fd3cc7d2 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Wed, 11 Oct 2023 23:05:58 -0500 Subject: [PATCH] pool: Do not populate/use gentx2. This modifies the pool to report the entire partial header via the first generation tx field as intended and no longer populates the second generation tx field since it does not apply to Decred. Some of the old ASICs hacked the stake version in there, but that is really not something that should have ever been done because the stratum "protocol" (which is not actually very well defined) already does not individually provide all of the information the Decred header needs nor does it provide an official way to extend it. Further, the Decred header explicitly provides additional space which removes the need to create a new coinbase and update the merkle root. So, in order to address these things, the field that was intended to serve for the coinbase (generate transaction) was repurposed to contain the serialized partial header for data after the previous block hash in the format it is to be hashed. Therefore, it should be providing the entire remaining partial header to ensure that any future modifications to the end of the header are available to miners without modification. Given that the old ASICs no longer work with the network, this takes the opportunity to make the change. --- cmd/miner/client.go | 4 ++-- pool/client.go | 9 ++++--- pool/client_test.go | 7 +++--- pool/hub.go | 7 +++--- pool/message.go | 57 +++++++++++++++++++++++++-------------------- 5 files changed, 44 insertions(+), 40 deletions(-) diff --git a/cmd/miner/client.go b/cmd/miner/client.go index f474c461..389b5ba2 100644 --- a/cmd/miner/client.go +++ b/cmd/miner/client.go @@ -371,7 +371,7 @@ func (m *Miner) process(ctx context.Context) { continue } - jobID, prevBlockE, genTx1E, genTx2E, blockVersionE, _, _, + jobID, prevBlockE, genTx1E, blockVersionE, _, _, cleanJob, err := pool.ParseWorkNotification(notif) if err != nil { log.Errorf("parse job notification error: %v", err) @@ -380,7 +380,7 @@ func (m *Miner) process(ctx context.Context) { } blockHeader, err := pool.GenerateBlockHeader(blockVersionE, - prevBlockE, genTx1E, m.extraNonce1E, genTx2E) + prevBlockE, genTx1E, m.extraNonce1E) if err != nil { log.Errorf("generate block header error: %v", err) m.cancel() diff --git a/pool/client.go b/pool/client.go index 5e9fb049..fa2b85d2 100644 --- a/pool/client.go +++ b/pool/client.go @@ -541,7 +541,7 @@ func (c *Client) handleSubmitWorkRequest(ctx context.Context, req *Request, allo // less than the pool target for the client. if hashTarget.Cmp(tgt) > 0 { err := fmt.Errorf("submitted work %s from %s is not less than its "+ - "corresponding pool target", hash.String(), id) + "corresponding pool target", hash, id) sErr := NewStratumError(LowDifficultyShare, err) resp := SubmitWorkResponse(*req.ID, false, sErr) c.sendMessage(resp) @@ -759,10 +759,9 @@ func (c *Client) updateWork(cleanJob bool) { updatedWorkE := buf.String() blockVersion := updatedWorkE[:8] prevBlock := updatedWorkE[8:72] - genTx1 := updatedWorkE[72:288] + genTx1 := updatedWorkE[72:360] nBits := updatedWorkE[232:240] nTime := updatedWorkE[272:280] - genTx2 := updatedWorkE[352:360] heightD, err := hex.DecodeString(updatedWorkE[256:264]) if err != nil { @@ -779,8 +778,8 @@ func (c *Client) updateWork(cleanJob bool) { log.Error(err) return } - workNotif := WorkNotification(job.UUID, prevBlock, genTx1, genTx2, - blockVersion, nBits, nTime, cleanJob) + workNotif := WorkNotification(job.UUID, prevBlock, genTx1, blockVersion, + nBits, nTime, cleanJob) c.sendMessage(workNotif) log.Tracef("Sent a timestamp-rolled current work at "+ diff --git a/pool/client_test.go b/pool/client_test.go index aa760d9b..48d22953 100644 --- a/pool/client_test.go +++ b/pool/client_test.go @@ -440,14 +440,13 @@ func testClientMessageHandling(t *testing.T) { blockVersion := workE[:8] prevBlock := workE[8:72] - genTx1 := workE[72:288] + genTx1 := workE[72:360] nBits := workE[232:240] nTime := workE[272:280] - genTx2 := workE[352:360] // Send a work notification to the CPU client. - r = WorkNotification(job.UUID, prevBlock, genTx1, genTx2, - blockVersion, nBits, nTime, true) + r = WorkNotification(job.UUID, prevBlock, genTx1, blockVersion, nBits, + nTime, true) select { case <-client.ctx.Done(): t.Fatalf("client context done: %v", err) diff --git a/pool/hub.go b/pool/hub.go index ff40d9d5..6d52b0c5 100644 --- a/pool/hub.go +++ b/pool/hub.go @@ -543,18 +543,17 @@ func (h *Hub) processWork(headerE string) { blockVersion := headerE[:8] prevBlock := headerE[8:72] - genTx1 := headerE[72:288] + genTx1 := headerE[72:360] nBits := headerE[232:240] nTime := headerE[272:280] - genTx2 := headerE[352:360] job := NewJob(headerE, height) err = h.cfg.DB.persistJob(job) if err != nil { log.Error(err) return } - workNotif := WorkNotification(job.UUID, prevBlock, genTx1, genTx2, - blockVersion, nBits, nTime, true) + workNotif := WorkNotification(job.UUID, prevBlock, genTx1, blockVersion, + nBits, nTime, true) h.endpoint.clientsMtx.Lock() for _, client := range h.endpoint.clients { client.sendMessage(workNotif) diff --git a/pool/message.go b/pool/message.go index b42cbe79..3eea75c4 100644 --- a/pool/message.go +++ b/pool/message.go @@ -10,7 +10,6 @@ import ( "encoding/json" "fmt" "math/big" - "strings" "github.com/decred/dcrd/wire" @@ -473,7 +472,9 @@ func ParseSetDifficultyNotification(req *Request) (uint64, error) { } // WorkNotification creates a work notification message. -func WorkNotification(jobID string, prevBlock string, genTx1 string, genTx2 string, blockVersion string, nBits string, nTime string, cleanJob bool) *Request { +func WorkNotification(jobID string, prevBlock string, genTx1 string, blockVersion string, nBits string, nTime string, cleanJob bool) *Request { + // The genTx2 parameter is not needed in Decred, so it is left blank. + const genTx2 = "" return &Request{ Method: Notify, Params: []interface{}{jobID, prevBlock, genTx1, genTx2, []string{}, @@ -482,49 +483,45 @@ func WorkNotification(jobID string, prevBlock string, genTx1 string, genTx2 stri } // ParseWorkNotification resolves a work notification message into its components. -func ParseWorkNotification(req *Request) (string, string, string, string, string, string, string, bool, error) { +func ParseWorkNotification(req *Request) (string, string, string, string, string, string, bool, error) { const funcName = "ParseWorkNotification" if req.Method != Notify { desc := fmt.Sprintf("%s: notification method is not notify", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } params, ok := req.Params.([]interface{}) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification "+ "parameters", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } jobID, ok := params[0].(string) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification "+ "jobID parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } prevBlock, ok := params[1].(string) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification prevBlock "+ "parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } genTx1, ok := params[2].(string) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification genTx1 "+ "parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } - genTx2, ok := params[3].(string) - if !ok { - desc := fmt.Sprintf("%s: unable to parse work notification genTx2 "+ - "parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) - } + // Note that params[3] which is a second generation transaction is not + // applicable for decred. - // Note that param[4] which is the list of merkle branches is not + // Note that params[4] which is the list of merkle branches is not // applicable for decred, the final merkle root is already // included in the block. @@ -532,46 +529,56 @@ func ParseWorkNotification(req *Request) (string, string, string, string, string if !ok { desc := fmt.Sprintf("%s: unable to parse work notification "+ "blockVersion parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } nBits, ok := params[6].(string) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification "+ "nBits parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } nTime, ok := params[7].(string) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification "+ "nTime parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } cleanJob, ok := params[8].(bool) if !ok { desc := fmt.Sprintf("%s: unable to parse work notification "+ "cleanJob parameter", funcName) - return "", "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) + return "", "", "", "", "", "", false, errs.MsgError(errs.Parse, desc) } - return jobID, prevBlock, genTx1, genTx2, blockVersion, - nBits, nTime, cleanJob, nil + return jobID, prevBlock, genTx1, blockVersion, nBits, nTime, cleanJob, nil } // GenerateBlockHeader creates a block header from a mining.notify // message and the extraNonce1 of the client. func GenerateBlockHeader(blockVersionE string, prevBlockE string, - genTx1E string, extraNonce1E string, genTx2E string) (*wire.BlockHeader, error) { + genTx1E string, extraNonce1E string) (*wire.BlockHeader, error) { + const funcName = "GenerateBlockHeader" + + // Ensure the provide generation tx (partial header) has the minimum + // required length. + const minGenTx1ELen = 288 + if len(genTx1E) < minGenTx1ELen { + desc := fmt.Sprintf("%s: genTx1E field length of %d is less than the "+ + "required minimum length of %d", funcName, len(genTx1E), + minGenTx1ELen) + return nil, errs.MsgError(errs.Decode, desc) + } + var buf bytes.Buffer _, _ = buf.WriteString(blockVersionE) _, _ = buf.WriteString(prevBlockE) - _, _ = buf.WriteString(genTx1E) + _, _ = buf.WriteString(genTx1E[:216]) _, _ = buf.WriteString(extraNonce1E) - _, _ = buf.WriteString(strings.Repeat("0", 56)) - _, _ = buf.WriteString(genTx2E) + _, _ = buf.WriteString(genTx1E[224:]) headerE := buf.String() headerD, err := hex.DecodeString(headerE)