Skip to content

Commit

Permalink
retry keystone on failure
Browse files Browse the repository at this point in the history
* if a keystone fails to mine, retry

* check if the keystone has already been mined

* allow a queue of 10 keystones, dropping the oldest if full and a new one comes in
  • Loading branch information
ClaytonNorthey92 committed Feb 28, 2024
1 parent 7875a89 commit 361f695
Show file tree
Hide file tree
Showing 5 changed files with 612 additions and 26 deletions.
3 changes: 2 additions & 1 deletion api/bfgapi/bfgapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
309 changes: 309 additions & 0 deletions e2e/e2e_ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion service/bfg/bfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 361f695

Please sign in to comment.