diff --git a/api/bfgapi/bfgapi.go b/api/bfgapi/bfgapi.go index 5758aeb7e..8290bce2c 100644 --- a/api/bfgapi/bfgapi.go +++ b/api/bfgapi/bfgapi.go @@ -127,7 +127,8 @@ type BitcoinUTXOsResponse struct { } type PopTxsForL2BlockRequest struct { - L2Block api.ByteSlice `json:"l2_block"` + L2Block api.ByteSlice `json:"l2_block"` + IncludeUnconfirmed bool `json:"include_unconfirmed"` } type PopTxsForL2BlockResponse struct { diff --git a/e2e/e2e_ext_test.go b/e2e/e2e_ext_test.go index ce694d0bf..85354b15c 100644 --- a/e2e/e2e_ext_test.go +++ b/e2e/e2e_ext_test.go @@ -2256,6 +2256,315 @@ func TestPopPayouts(t *testing.T) { } } +func TestPopTxsForL2BlockConfirmed(t *testing.T) { + db, pgUri, sdb, cleanup := createTestDB(context.Background(), t) + defer func() { + db.Close() + sdb.Close() + cleanup() + }() + + ctx, cancel := defaultTestContext() + defer cancel() + + privateKey := dcrsecp256k1.PrivKeyFromBytes([]byte{9, 8, 7}) + publicKey := privateKey.PubKey() + publicKeyUncompressed := publicKey.SerializeUncompressed() + minerHash := crypto.Keccak256(publicKeyUncompressed[1:]) + minerHash = minerHash[len(minerHash)-20:] + + privateKey = dcrsecp256k1.PrivKeyFromBytes([]byte{1, 2, 3}) + publicKey = privateKey.PubKey() + otherPublicKeyUncompressed := publicKey.SerializeUncompressed() + minerHash = crypto.Keccak256(otherPublicKeyUncompressed[1:]) + minerHash = minerHash[len(minerHash)-20:] + + l2Keystone := hemi.L2Keystone{ + Version: 1, + L1BlockNumber: 11, + L2BlockNumber: 22, + ParentEPHash: fillOutBytes("parentephash", 32), + PrevKeystoneEPHash: fillOutBytes("prevkeystoneephash", 32), + StateRoot: fillOutBytes("stateroot", 32), + EPHash: fillOutBytes("ephash", 32), + } + + btcHeaderHash := fillOutBytes("btcheaderhash", 32) + + btcBlock := bfgd.BtcBlock{ + Hash: btcHeaderHash, + Header: fillOutBytes("btcheader", 80), + Height: 99, + } + + err := db.BtcBlockInsert(ctx, &btcBlock) + if err != nil { + t.Fatal(err) + } + + // insert 2 pop bases, 1 confirmed, 1 unconfirmed + + var txIndex uint64 = 1 + + popBasis := bfgd.PopBasis{ + BtcTxId: fillOutBytes("btctxid1", 32), + BtcRawTx: []byte("btcrawtx1"), + PopTxId: fillOutBytes("poptxid1", 32), + L2KeystoneAbrevHash: hemi.L2KeystoneAbbreviate(l2Keystone).Hash(), + PopMinerPublicKey: publicKeyUncompressed, + BtcHeaderHash: btcHeaderHash, + BtcTxIndex: &txIndex, + } + + err = db.PopBasisInsertFull(ctx, &popBasis) + if err != nil { + t.Fatal(err) + } + + txIndex = 2 + + popBasis = bfgd.PopBasis{ + BtcTxId: fillOutBytes("btctxid2", 32), + BtcRawTx: []byte("btcrawtx2"), + PopTxId: fillOutBytes("poptxid2", 32), + L2KeystoneAbrevHash: hemi.L2KeystoneAbbreviate(l2Keystone).Hash(), + PopMinerPublicKey: otherPublicKeyUncompressed, + BtcHeaderHash: btcHeaderHash, + BtcTxIndex: &txIndex, + } + + err = db.PopBasisInsertPopMFields(ctx, &popBasis) + if err != nil { + t.Fatal(err) + } + + _, _, bfgWsurl, _ := createBfgServer(ctx, t, pgUri, "", 1) + + c, _, err := websocket.Dial(ctx, bfgWsurl, nil) + if err != nil { + t.Fatal(err) + } + defer c.CloseNow() + + assertPing(ctx, t, c, bfgapi.CmdPingRequest) + + bws := &bfgWs{ + conn: protocol.NewWSConn(c), + } + + serializedL2Keystone := hemi.L2KeystoneAbbreviate(l2Keystone).Serialize() + + popTxRequest := bfgapi.PopTxsForL2BlockRequest{ + L2Block: serializedL2Keystone[:], + } + + err = bfgapi.Write(ctx, bws.conn, "someid", popTxRequest) + if err != nil { + t.Fatal(err) + } + + var v protocol.Message + err = wsjson.Read(ctx, c, &v) + if err != nil { + t.Fatal(err) + } + + if v.Header.Command != bfgapi.CmdPopTxForL2BlockResponse { + t.Fatalf("received unexpected command: %s", v.Header.Command) + } + + popTxResponse := bfgapi.PopTxsForL2BlockResponse{} + err = json.Unmarshal(v.Payload, &popTxResponse) + if err != nil { + t.Fatal(err) + } + + popTxsDb, err := db.PopBasisByL2KeystoneAbrevHash(ctx, [32]byte(hemi.HashSerializedL2KeystoneAbrev(popTxRequest.L2Block)), true) + if err != nil { + t.Fatal(err) + } + + if len(popTxsDb) != 1 { + t.Fatalf("expected there to be 1 confirmed pop tx, received %d", len(popTxsDb)) + } + + popTxs := make([]bfgapi.PopTx, 0, len(popTxsDb)) + + for k := range popTxsDb { + popTxs = append(popTxs, bfgapi.PopTx{ + BtcTxId: api.ByteSlice(popTxsDb[k].BtcTxId), + BtcRawTx: api.ByteSlice(popTxsDb[k].BtcRawTx), + BtcHeaderHash: api.ByteSlice(popTxsDb[k].BtcHeaderHash), + BtcTxIndex: popTxsDb[k].BtcTxIndex, + BtcMerklePath: popTxsDb[k].BtcMerklePath, + PopTxId: api.ByteSlice(popTxsDb[k].PopTxId), + PopMinerPublicKey: api.ByteSlice(popTxsDb[k].PopMinerPublicKey), + L2KeystoneAbrevHash: api.ByteSlice(popTxsDb[k].L2KeystoneAbrevHash), + }) + } + + diff := deep.Equal(popTxResponse.PopTxs, popTxs) + + if len(diff) != 0 { + t.Fatalf("unexpected diff %s", diff) + } +} + +func TestPopTxsForL2BlockUnconfirmed(t *testing.T) { + db, pgUri, sdb, cleanup := createTestDB(context.Background(), t) + defer func() { + db.Close() + sdb.Close() + cleanup() + }() + + ctx, cancel := defaultTestContext() + defer cancel() + + privateKey := dcrsecp256k1.PrivKeyFromBytes([]byte{9, 8, 7}) + publicKey := privateKey.PubKey() + publicKeyUncompressed := publicKey.SerializeUncompressed() + minerHash := crypto.Keccak256(publicKeyUncompressed[1:]) + minerHash = minerHash[len(minerHash)-20:] + + privateKey = dcrsecp256k1.PrivKeyFromBytes([]byte{1, 2, 3}) + publicKey = privateKey.PubKey() + otherPublicKeyUncompressed := publicKey.SerializeUncompressed() + minerHash = crypto.Keccak256(otherPublicKeyUncompressed[1:]) + minerHash = minerHash[len(minerHash)-20:] + + l2Keystone := hemi.L2Keystone{ + Version: 1, + L1BlockNumber: 11, + L2BlockNumber: 22, + ParentEPHash: fillOutBytes("parentephash", 32), + PrevKeystoneEPHash: fillOutBytes("prevkeystoneephash", 32), + StateRoot: fillOutBytes("stateroot", 32), + EPHash: fillOutBytes("ephash", 32), + } + + btcHeaderHash := fillOutBytes("btcheaderhash", 32) + + btcBlock := bfgd.BtcBlock{ + Hash: btcHeaderHash, + Header: fillOutBytes("btcheader", 80), + Height: 99, + } + + err := db.BtcBlockInsert(ctx, &btcBlock) + if err != nil { + t.Fatal(err) + } + + // insert 2 pop bases, 1 confirmed, 1 unconfirmed + + var txIndex uint64 = 1 + + popBasis := bfgd.PopBasis{ + BtcTxId: fillOutBytes("btctxid1", 32), + BtcRawTx: []byte("btcrawtx1"), + PopTxId: fillOutBytes("poptxid1", 32), + L2KeystoneAbrevHash: hemi.L2KeystoneAbbreviate(l2Keystone).Hash(), + PopMinerPublicKey: publicKeyUncompressed, + BtcHeaderHash: btcHeaderHash, + BtcTxIndex: &txIndex, + } + + err = db.PopBasisInsertFull(ctx, &popBasis) + if err != nil { + t.Fatal(err) + } + + txIndex = 2 + + popBasis = bfgd.PopBasis{ + BtcTxId: fillOutBytes("btctxid2", 32), + BtcRawTx: []byte("btcrawtx2"), + PopTxId: fillOutBytes("poptxid2", 32), + L2KeystoneAbrevHash: hemi.L2KeystoneAbbreviate(l2Keystone).Hash(), + PopMinerPublicKey: otherPublicKeyUncompressed, + BtcHeaderHash: btcHeaderHash, + BtcTxIndex: &txIndex, + } + + err = db.PopBasisInsertPopMFields(ctx, &popBasis) + if err != nil { + t.Fatal(err) + } + + _, _, bfgWsurl, _ := createBfgServer(ctx, t, pgUri, "", 1) + + c, _, err := websocket.Dial(ctx, bfgWsurl, nil) + if err != nil { + t.Fatal(err) + } + defer c.CloseNow() + + assertPing(ctx, t, c, bfgapi.CmdPingRequest) + + bws := &bfgWs{ + conn: protocol.NewWSConn(c), + } + + serializedL2Keystone := hemi.L2KeystoneAbbreviate(l2Keystone).Serialize() + + popTxRequest := bfgapi.PopTxsForL2BlockRequest{ + L2Block: serializedL2Keystone[:], + IncludeUnconfirmed: true, + } + + err = bfgapi.Write(ctx, bws.conn, "someid", popTxRequest) + if err != nil { + t.Fatal(err) + } + + var v protocol.Message + err = wsjson.Read(ctx, c, &v) + if err != nil { + t.Fatal(err) + } + + if v.Header.Command != bfgapi.CmdPopTxForL2BlockResponse { + t.Fatalf("received unexpected command: %s", v.Header.Command) + } + + popTxResponse := bfgapi.PopTxsForL2BlockResponse{} + err = json.Unmarshal(v.Payload, &popTxResponse) + if err != nil { + t.Fatal(err) + } + + popTxsDb, err := db.PopBasisByL2KeystoneAbrevHash(ctx, [32]byte(hemi.HashSerializedL2KeystoneAbrev(popTxRequest.L2Block)), false) + if err != nil { + t.Fatal(err) + } + + if len(popTxsDb) != 2 { + t.Fatalf("expected there to be 2 unconfirmed pop tx, received %d", len(popTxsDb)) + } + + popTxs := make([]bfgapi.PopTx, 0, len(popTxsDb)) + + for k := range popTxsDb { + popTxs = append(popTxs, bfgapi.PopTx{ + BtcTxId: api.ByteSlice(popTxsDb[k].BtcTxId), + BtcRawTx: api.ByteSlice(popTxsDb[k].BtcRawTx), + BtcHeaderHash: api.ByteSlice(popTxsDb[k].BtcHeaderHash), + BtcTxIndex: popTxsDb[k].BtcTxIndex, + BtcMerklePath: popTxsDb[k].BtcMerklePath, + PopTxId: api.ByteSlice(popTxsDb[k].PopTxId), + PopMinerPublicKey: api.ByteSlice(popTxsDb[k].PopMinerPublicKey), + L2KeystoneAbrevHash: api.ByteSlice(popTxsDb[k].L2KeystoneAbrevHash), + }) + } + + diff := deep.Equal(popTxResponse.PopTxs, popTxs) + + if len(diff) != 0 { + t.Fatalf("unexpected diff %s", diff) + } +} + func TestGetMostRecentL2BtcFinalitiesBSS(t *testing.T) { db, pgUri, sdb, cleanup := createTestDB(context.Background(), t) defer func() { diff --git a/service/bfg/bfg.go b/service/bfg/bfg.go index 305a145e0..9912bbd16 100644 --- a/service/bfg/bfg.go +++ b/service/bfg/bfg.go @@ -994,7 +994,7 @@ func (s *Server) handlePopTxForL2Block(ctx context.Context, bws *bfgWs, payload hash := hemi.HashSerializedL2KeystoneAbrev(p.L2Block) var h [32]byte copy(h[:], hash) - popTxs, err := s.db.PopBasisByL2KeystoneAbrevHash(ctx, h, true) + popTxs, err := s.db.PopBasisByL2KeystoneAbrevHash(ctx, h, !p.IncludeUnconfirmed) if err != nil { ie := NewInternalErrorf("error getting pop basis: %s", err) response.Error = ie.internal diff --git a/service/popm/popm.go b/service/popm/popm.go index 3b2e37b87..abf841764 100644 --- a/service/popm/popm.go +++ b/service/popm/popm.go @@ -50,6 +50,33 @@ func init() { loggo.ConfigureLoggers(logLevel) } +type CircularFifo struct { + buf chan hemi.L2Keystone + mtx sync.Mutex +} + +func NewCircularFifo(n int) *CircularFifo { + return &CircularFifo{ + buf: make(chan hemi.L2Keystone, n), + } +} + +func (r *CircularFifo) Push(val hemi.L2Keystone) { + // if the channel is full, remove the oldest item. according to Go's docs + // "A single channel may be used in send statements, receive operations, and + // calls to the built-in functions cap and len by any number of + // goroutines without further synchronization" + if len(r.buf) == cap(r.buf) { + <-r.buf + } + + r.buf <- val +} + +func (r *CircularFifo) Pop() <-chan hemi.L2Keystone { + return r.buf +} + type Config struct { // BFGWSURL specifies the URL of the BFG private websocket endpoint BFGWSURL string @@ -94,7 +121,7 @@ type Miner struct { btcAddress *btcutil.AddressPubKeyHash lastKeystone *hemi.L2Keystone - keystoneCh chan *hemi.L2Keystone + keystoneBuf *CircularFifo // Prometheus isRunning bool @@ -110,7 +137,7 @@ func NewMiner(cfg *Config) (*Miner, error) { m := &Miner{ cfg: cfg, - keystoneCh: make(chan *hemi.L2Keystone, 3), + keystoneBuf: NewCircularFifo(10), bfgCmdCh: make(chan bfgCmd, 10), holdoffTimeout: 5 * time.Second, requestTimeout: 5 * time.Second, @@ -253,6 +280,33 @@ func createTx(l2Keystone *hemi.L2Keystone, btcHeight uint64, utxo *bfgapi.Bitcoi return &btx, nil } +func (m *Miner) shouldMineKeystone(ctx context.Context, ks *hemi.L2Keystone) (bool, error) { + serialized := hemi.L2KeystoneAbbreviate(*ks).Serialize() + response, err := m.callBFG(ctx, 5*time.Second, &bfgapi.PopTxsForL2BlockRequest{ + L2Block: hemi.HashSerializedL2KeystoneAbrev( + serialized[:], + ), + IncludeUnconfirmed: false, + }) + if err != nil { + return false, err + } + + popTxsForL2BlockResponse, ok := response.(*bfgapi.PopTxsForL2BlockResponse) + if !ok { + return false, errors.New("not bfgapi.PopTxsForL2BlockResponse") + } + + // this response only returns confirmed keystones, if there any then do not + // mine this block + if popTxsForL2BlockResponse.PopTxs != nil && len(popTxsForL2BlockResponse.PopTxs) > 0 { + log.Infof("l2 keystone already confirmed in btc chain, skipping") + return false, nil + } + + return true, nil +} + // XXX this function is not right. Clean it up and ensure we make this in at // least 2 functions. This needs to create and sign a tx, and then broadcast // seperately. Also utxo picker needs to be fixed. Don't return a fake utxo @@ -438,23 +492,40 @@ func (m *Miner) BitcoinUTXOs(ctx context.Context, scriptHash string) (*bfgapi.Bi func (m *Miner) mine(ctx context.Context) { defer m.wg.Done() for { + log.Infof("checking keystone...") select { case <-ctx.Done(): return - case ks := <-m.keystoneCh: - log.Tracef("Received new keystone header for mining with height %v...", ks.L2BlockNumber) - if err := m.mineKeystone(ctx, ks); err != nil { - log.Errorf("Failed to mine keystone: %v", err) + case ks := <-m.keystoneBuf.Pop(): + log.Infof("Received new keystone header for mining with height %v...", ks.L2BlockNumber) + for { + shouldMine, err := m.shouldMineKeystone(ctx, &ks) + if err != nil { + log.Errorf("error determining if should mine keystone: %s", err) + continue + } + + if !shouldMine { + break + } + + if err := m.mineKeystone(ctx, &ks); err != nil { + log.Errorf("Failed to mine keystone: %v", err) + select { + case <-time.After(500 * time.Millisecond): + case <-ctx.Done(): + return + } + } else { + break + } } } } } func (m *Miner) queueKeystoneForMining(keystone *hemi.L2Keystone) { - select { - case m.keystoneCh <- keystone: - default: - } + m.keystoneBuf.Push(*keystone) } func sortL2KeystonesByL2BlockNumberAsc(a, b hemi.L2Keystone) int { diff --git a/service/popm/popm_test.go b/service/popm/popm_test.go index 3f3129185..f46412d9e 100644 --- a/service/popm/popm_test.go +++ b/service/popm/popm_test.go @@ -138,7 +138,9 @@ func TestProcessReceivedKeystones(t *testing.T) { }, } - miner := Miner{} + miner := Miner{ + keystoneBuf: NewCircularFifo(10), + } miner.processReceivedKeystones(context.Background(), firstBatchOfL2Keystones) diff := deep.Equal(*miner.lastKeystone, hemi.L2Keystone{ @@ -498,8 +500,8 @@ func TestProcessReceivedInAscOrder(t *testing.T) { for { select { - case l2Keystone := <-miner.keystoneCh: - receivedKeystones = append(receivedKeystones, *l2Keystone) + case l2Keystone := <-miner.keystoneBuf.Pop(): + receivedKeystones = append(receivedKeystones, l2Keystone) continue default: break @@ -513,6 +515,87 @@ func TestProcessReceivedInAscOrder(t *testing.T) { } } +// TestProcessReceivedInAscOrder ensures that if we queue more than 10 keystones +// for mining, that we override the oldest +func TestProcessReceivedInAscOrderOverride(t *testing.T) { + keystones := []hemi.L2Keystone{ + { + L2BlockNumber: 1, + EPHash: []byte{1}, + }, + { + L2BlockNumber: 2, + EPHash: []byte{2}, + }, + { + L2BlockNumber: 3, + EPHash: []byte{3}, + }, + { + L2BlockNumber: 4, + EPHash: []byte{4}, + }, + { + L2BlockNumber: 5, + EPHash: []byte{5}, + }, + { + L2BlockNumber: 6, + EPHash: []byte{6}, + }, + { + L2BlockNumber: 7, + EPHash: []byte{7}, + }, + { + L2BlockNumber: 8, + EPHash: []byte{8}, + }, + { + L2BlockNumber: 9, + EPHash: []byte{9}, + }, + { + L2BlockNumber: 10, + EPHash: []byte{10}, + }, + { + L2BlockNumber: 11, + EPHash: []byte{11}, + }, + } + + miner, err := NewMiner(&Config{ + BTCPrivateKey: "ebaaedce6af48a03bbfd25e8cd0364140ebaaedce6af48a03bbfd25e8cd03641", + BTCChainName: "testnet3", + }) + if err != nil { + t.Fatal(err) + } + + for _, keystone := range keystones { + miner.processReceivedKeystones(context.Background(), []hemi.L2Keystone{keystone}) + } + + receivedKeystones := []hemi.L2Keystone{} + + for { + select { + case l2Keystone := <-miner.keystoneBuf.Pop(): + receivedKeystones = append(receivedKeystones, l2Keystone) + continue + default: + break + } + break + } + + diff := deep.Equal(keystones[len(keystones)-10:], receivedKeystones) + if len(diff) != 0 { + t.Fatalf("received unexpected diff: %s", diff) + } +} + func TestConnectToBFGAndPerformMineWithAuth(t *testing.T) { privateKey, err := dcrsecp256k1.GeneratePrivateKey() if err != nil { @@ -523,7 +606,7 @@ func TestConnectToBFGAndPerformMineWithAuth(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - server, msgCh, cleanup := createMockBFG(ctx, t, []string{publicKey}) + server, msgCh, cleanup := createMockBFG(ctx, t, []string{publicKey}, false, 1) defer cleanup() go func() { @@ -554,6 +637,7 @@ func TestConnectToBFGAndPerformMineWithAuth(t *testing.T) { bfgapi.CmdBitcoinBalanceRequest, bfgapi.CmdBitcoinUTXOsRequest, bfgapi.CmdBitcoinBroadcastRequest, + bfgapi.CmdPopTxForL2BlockRequest, } for { @@ -587,7 +671,7 @@ func TestConnectToBFGAndPerformMine(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - server, msgCh, cleanup := createMockBFG(ctx, t, []string{}) + server, msgCh, cleanup := createMockBFG(ctx, t, []string{}, false, 1) defer cleanup() go func() { @@ -618,6 +702,7 @@ func TestConnectToBFGAndPerformMine(t *testing.T) { bfgapi.CmdBitcoinBalanceRequest, bfgapi.CmdBitcoinUTXOsRequest, bfgapi.CmdBitcoinBroadcastRequest, + bfgapi.CmdPopTxForL2BlockRequest, } for { @@ -643,6 +728,112 @@ func TestConnectToBFGAndPerformMine(t *testing.T) { } } +func TestConnectToBFGAndPerformMineMultiple(t *testing.T) { + privateKey, err := dcrsecp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + server, msgCh, cleanup := createMockBFG(ctx, t, []string{}, false, 2) + defer cleanup() + + go func() { + miner, err := NewMiner(&Config{ + BFGWSURL: server.URL + bfgapi.RouteWebsocketPublic, + BTCChainName: "testnet3", + BTCPrivateKey: hex.EncodeToString(privateKey.Serialize()), + }) + if err != nil { + panic(err) + } + + err = miner.Run(ctx) + if err != nil && err != context.Canceled { + panic(err) + } + }() + + // we can't guarantee order here, so test that we get all expected messages + // from popm within the timeout + + messagesReceived := make(map[string]int) + + messagesExpected := map[protocol.Command]int{ + EventConnected: 1, + bfgapi.CmdL2KeystonesRequest: 1, + bfgapi.CmdBitcoinInfoRequest: 2, + bfgapi.CmdBitcoinBalanceRequest: 2, + bfgapi.CmdBitcoinUTXOsRequest: 2, + bfgapi.CmdBitcoinBroadcastRequest: 2, + bfgapi.CmdPopTxForL2BlockRequest: 2, + } + + for { + select { + case msg := <-msgCh: + t.Logf("received message %v", msg) + messagesReceived[msg]++ + case <-ctx.Done(): + if ctx.Err() != nil { + t.Fatal(ctx.Err()) + } + } + missing := false + for m := range messagesExpected { + message := fmt.Sprintf("%s", m) + if messagesReceived[message] != messagesExpected[m] { + t.Logf("still missing message %v, found %d want %d", m, messagesReceived[message], messagesExpected[m]) + missing = true + } + } + if missing == false { + break + } + } +} + +func TestConnectToBFGAndDoNotMineBecauseKeystoneExists(t *testing.T) { + privateKey, err := dcrsecp256k1.GeneratePrivateKey() + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + server, msgCh, cleanup := createMockBFG(ctx, t, []string{}, true, 1) + defer cleanup() + + go func() { + miner, err := NewMiner(&Config{ + BFGWSURL: server.URL + bfgapi.RouteWebsocketPublic, + BTCChainName: "testnet3", + BTCPrivateKey: hex.EncodeToString(privateKey.Serialize()), + }) + if err != nil { + panic(err) + } + + err = miner.Run(ctx) + }() + + var lastMessageReceived string + + for { + select { + case lastMessageReceived = <-msgCh: + t.Logf("received message %v", lastMessageReceived) + case <-ctx.Done(): + if lastMessageReceived != bfgapi.CmdPopTxForL2BlockRequest { + t.Fatalf("incorrect last message %s", lastMessageReceived) + } else { + return + } + } + } +} + func TestConnectToBFGAndPerformMineWithAuthError(t *testing.T) { privateKey, err := dcrsecp256k1.GeneratePrivateKey() if err != nil { @@ -651,7 +842,7 @@ func TestConnectToBFGAndPerformMineWithAuthError(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - server, msgCh, cleanup := createMockBFG(ctx, t, []string{"incorrect"}) + server, msgCh, cleanup := createMockBFG(ctx, t, []string{"incorrect"}, false, 1) defer cleanup() miner, err := NewMiner(&Config{ @@ -683,7 +874,7 @@ func TestConnectToBFGAndPerformMineWithAuthError(t *testing.T) { } } -func createMockBFG(ctx context.Context, t *testing.T, publicKeys []string) (*httptest.Server, chan string, func()) { +func createMockBFG(ctx context.Context, t *testing.T, publicKeys []string, keystoneMined bool, keystoneCount int) (*httptest.Server, chan string, func()) { msgCh := make(chan string) handler := func(w http.ResponseWriter, r *http.Request) { @@ -770,13 +961,13 @@ func createMockBFG(ctx context.Context, t *testing.T, publicKeys []string) (*htt }() if command == bfgapi.CmdL2KeystonesRequest { - if err := bfgapi.Write(ctx, conn, id, bfgapi.L2KeystonesResponse{ - L2Keystones: []hemi.L2Keystone{ - { - L2BlockNumber: 100, - }, - }, - }); err != nil { + response := bfgapi.L2KeystonesResponse{} + for i := 0; i < keystoneCount; i++ { + response.L2Keystones = append(response.L2Keystones, hemi.L2Keystone{ + L2BlockNumber: uint32(100 + i), + }) + } + if err := bfgapi.Write(ctx, conn, id, response); err != nil { if !errors.Is(ctx.Err(), context.Canceled) { panic(err) } @@ -835,6 +1026,20 @@ func createMockBFG(ctx context.Context, t *testing.T, publicKeys []string) (*htt } } + if command == bfgapi.CmdPopTxForL2BlockRequest { + response := bfgapi.PopTxsForL2BlockResponse{} + if keystoneMined { + response.PopTxs = []bfgapi.PopTx{ + {}, + } + } + if err := bfgapi.Write(ctx, conn, id, response); err != nil { + if !errors.Is(ctx.Err(), context.Canceled) { + panic(err) + } + } + } + } }