Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow connection of BFG to another BFG for L2 Keystone Notifications/Querying #286

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,28 @@ go run ./integrationtest
- A **PostgreSQL database**, bfgd expects the sql scripts in `./database/bfgd/scripts/` to be run to set up your schema.
- A **connection to an Electrs node** on the proper Bitcoin network (testnet or mainnet).

### Running your own Bitcoin Finality Governor (bfgd) and PoP mining with it

If you'd like to run your own `bfgd` and don't want to rely on Hemi Labs (or any third party) for _broadcasting transactions_, you may run `bfgd` and connect it to a _trusted_ `bfgd` run by a third party to _receive l2 keystones only_ (l2 keystones represent l2 state and are what are mined in PoP transactions). In this case, the third party `bfgd` will only send you l2 keystones, your `bfgd` can notify your local pop miner and this will broadcast them to your Electrs+bitcoind setup so you don't rely on Hemi Labs--or any third party--which may be congested.

You'll need the following running to do this:
* bitcoind
* electrs
* postgres
* bfgd

_Note: make sure you run all of the *.sql files for bfg in `database/bfgd/postgres/scripts`_

When running BFG, you'll want the following env variables set:

* `BFG_BFG_URL`: the _trusted_ `bfgd`'s websocket url that you will connect to
* `BFG_BTC_PRIVKEY`: your btc private key. note that this can be an unfunded private key and you'll still receive l2 keystones to mine
joshuasing marked this conversation as resolved.
Show resolved Hide resolved
* `BFG_POSTGRES_URI`: the connection URI for your postgres instance
* `BFG_BTC_START_HEIGHT`: when your db is empty, bfgd will need a starting point to parse btc blocks at, set this to the tip of the bitcoin chain at first deploy
* `BFG_EXBTC_ADDRESS`: your electrs rpc address

You may then connect your local `popmd` to your aforementioned local `bfgd` via the `POPM_BFG_URL` env variable

## ▶️ Running bssd

### 🏁 Prerequisites
Expand Down
12 changes: 12 additions & 0 deletions cmd/bfgd/bfgd.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ var (
Help: "list of headers used to obtain the client IP address (requires trusted proxies)",
Print: config.PrintAll,
},
"BFG_BFG_URL": config.Config{
Value: &cfg.BFGURL,
DefaultValue: "",
Help: "public websocket address of another BFG you'd like to receive L2Keystones from",
Print: config.PrintAll,
},
"BFG_BTC_PRIVKEY": config.Config{
Value: &cfg.BTCPrivateKey,
DefaultValue: "",
Help: "a btc private key, this is only needed when connecting to another BFG",
Print: config.PrintSecret,
},
}
)

Expand Down
87 changes: 85 additions & 2 deletions database/bfgd/database_ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"log"
mathrand "math/rand/v2"
"net/url"
"os"
Expand Down Expand Up @@ -475,6 +476,79 @@ func TestL2KeystoneInsertMultipleSuccess(t *testing.T) {
}
}

func TestL2KeystoneInsertIgnoreDuplicates(t *testing.T) {
ctx, cancel := defaultTestContext()
defer cancel()

db, sdb, cleanup := createTestDB(ctx, t)
defer func() {
db.Close()
sdb.Close()
cleanup()
}()

l2Keystone := bfgd.L2Keystone{
Version: 1,
L1BlockNumber: 11,
L2BlockNumber: 22,
ParentEPHash: fillOutBytes("parentephash", 32),
PrevKeystoneEPHash: fillOutBytes("prevkeystoneephash", 32),
StateRoot: fillOutBytes("stateroot", 32),
EPHash: fillOutBytes("ephash", 32),
Hash: fillOutBytes("mockhash", 32),
}

otherL2Keystone := bfgd.L2Keystone{
Version: 1,
L1BlockNumber: 11,
L2BlockNumber: 22,
ParentEPHash: fillOutBytes("parentephash", 32),
PrevKeystoneEPHash: fillOutBytes("prevkeystoneephash", 32),
StateRoot: fillOutBytes("stateroot", 32),
EPHash: fillOutBytes("ephash", 32),
Hash: fillOutBytes("mockhashz", 32),
}

err := db.L2KeystonesInsert(ctx, []bfgd.L2Keystone{l2Keystone, otherL2Keystone})
if err != nil {
t.Fatal(err)
}

err = db.L2KeystonesInsert(ctx, []bfgd.L2Keystone{l2Keystone, otherL2Keystone})
if err != nil {
t.Fatal(err)
}

saved, err := db.L2KeystoneByAbrevHash(ctx, [32]byte(l2Keystone.Hash))
if err != nil {
t.Fatal(err)
}

diff := deep.Equal(saved, &l2Keystone)
if len(diff) != 0 {
t.Fatalf("unexpected diff %s", diff)
}

otherSaved, err := db.L2KeystoneByAbrevHash(ctx, [32]byte(otherL2Keystone.Hash))
if err != nil {
t.Fatal(err)
}

diff = deep.Equal(otherSaved, &otherL2Keystone)
if len(diff) != 0 {
t.Fatalf("unexpected diff %s", diff)
}

count, err := l2KeystonesCount(ctx, sdb)
if err != nil {
t.Fatal(err)
}

if count != 2 {
t.Fatalf("unexpected count %d", count)
}
}

func TestL2KeystoneInsertInvalidHashLength(t *testing.T) {
ctx, cancel := defaultTestContext()
defer cancel()
Expand Down Expand Up @@ -915,7 +989,7 @@ func TestL2KeystoneInsertMultipleAtomicFailure(t *testing.T) {
}
}

func TestL2KeystoneInsertMultipleDuplicateError(t *testing.T) {
func TestL2KeystoneInsertDuplicateOK(t *testing.T) {
ctx, cancel := defaultTestContext()
defer cancel()

Expand Down Expand Up @@ -949,9 +1023,18 @@ func TestL2KeystoneInsertMultipleDuplicateError(t *testing.T) {
}

err := db.L2KeystonesInsert(ctx, []bfgd.L2Keystone{l2Keystone, otherL2Keystone})
if err == nil || errors.Is(err, database.DuplicateError("")) == false {
if err != nil {
t.Fatalf("received unexpected error: %s", err)
}

l2Keystones, err := db.L2KeystonesMostRecentN(ctx, 5)
if err != nil {
t.Fatal(err)
}

if diff := deep.Equal([]bfgd.L2Keystone{l2Keystone}, l2Keystones); len(diff) != 0 {
log.Fatalf("unexpected diff %v", diff)
}
}

func TestPopBasisInsertNilMerklePath(t *testing.T) {
Expand Down
11 changes: 3 additions & 8 deletions database/bfgd/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ func (p *pgdb) L2KeystonesInsert(ctx context.Context, l2ks []bfgd.L2Keystone) er
)

VALUES ($1, $2, $3, $4, $5, $6, $7, $8)

ON CONFLICT DO NOTHING
`

for _, v := range l2ks {
result, err := tx.ExecContext(ctx, qInsertL2Keystone, v.Hash,
_, err := tx.ExecContext(ctx, qInsertL2Keystone, v.Hash,
v.L1BlockNumber, v.L2BlockNumber, v.ParentEPHash,
v.PrevKeystoneEPHash, v.StateRoot, v.EPHash, v.Version)
if err != nil {
Expand All @@ -156,13 +158,6 @@ func (p *pgdb) L2KeystonesInsert(ctx context.Context, l2ks []bfgd.L2Keystone) er
}
return fmt.Errorf("insert l2 keystone: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("insert l2 keystone rows affected: %w", err)
}
if rows < 1 {
return fmt.Errorf("insert l2 keystone rows: %v", rows)
}
}

err = tx.Commit()
Expand Down
Loading