Skip to content

Commit

Permalink
popm/wasm: add events and clean up globals (#175)
Browse files Browse the repository at this point in the history
 - Add events to the `popm` package.
 
 - Add events to the WebAssembly PoP Miner and the `@hemilabs/pop-miner`
   package.
 
 - Rename `activeMiner()` to `runningMiner()`.
 
 - Add a `Service` type and use `svc` global variable (replaces `pm` and
   `pmMtx`). This allows us to have a global struct to store data in, 
   without the need to use a lock to access the data.

 - Add 4 initial events: `minerStart`, `minerStop`, `mineKeystone`, and 
   `transactionBroadcast`.

Closes #150
  • Loading branch information
joshuasing authored Jul 11, 2024
1 parent a237239 commit 9c4cea5
Show file tree
Hide file tree
Showing 11 changed files with 560 additions and 83 deletions.
57 changes: 57 additions & 0 deletions service/popm/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) 2024 Hemi Labs, Inc.
// Use of this source code is governed by the MIT License,
// which can be found in the LICENSE file.

package popm

import (
"github.com/hemilabs/heminetwork/hemi"
)

// EventHandler is a function that can handle an event.
type EventHandler = func(event EventType, data any)

// EventType represents a type of event.
type EventType int

const (
// EventTypeMineKeystone is an event dispatched when a L2 keystone is being
// mined.
EventTypeMineKeystone EventType = iota + 1

// EventTypeTransactionBroadcast is an event dispatched when a Bitcoin
// transaction has been broadcast to the network.
EventTypeTransactionBroadcast
)

// EventMineKeystone is the data for EventTypeMineKeystone.
type EventMineKeystone struct {
Keystone *hemi.L2Keystone
}

// EventTransactionBroadcast is the data for EventTypeTransactionBroadcast.
type EventTransactionBroadcast struct {
Keystone *hemi.L2Keystone
TxHash string
}

// RegisterEventHandler registers an event handler to receive all events
// dispatched by the miner. The dispatched events can be filtered by EventType
// when received.
func (m *Miner) RegisterEventHandler(handler EventHandler) {
m.eventHandlersMtx.Lock()
defer m.eventHandlersMtx.Unlock()
m.eventHandlers = append(m.eventHandlers, handler)
}

// dispatchEvent calls all registered event handlers with the given eventType
// and data. It is recommended to call this function in a go routine to avoid
// blocking operation while the event is being dispatched, as all event handlers
// will be executed synchronously.
func (m *Miner) dispatchEvent(eventType EventType, data any) {
m.eventHandlersMtx.RLock()
defer m.eventHandlersMtx.RUnlock()
for _, handler := range m.eventHandlers {
handler(eventType, data)
}
}
8 changes: 8 additions & 0 deletions service/popm/popm.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ type Miner struct {
mineNowCh chan struct{}

l2Keystones map[string]L2KeystoneProcessingContainer

eventHandlersMtx sync.RWMutex
eventHandlers []EventHandler
}

func NewMiner(cfg *Config) (*Miner, error) {
Expand Down Expand Up @@ -318,6 +321,8 @@ func createTx(l2Keystone *hemi.L2Keystone, btcHeight uint64, utxo *bfgapi.Bitcoi
func (m *Miner) mineKeystone(ctx context.Context, ks *hemi.L2Keystone) error {
log.Infof("Broadcasting PoP transaction to Bitcoin...")

go m.dispatchEvent(EventTypeMineKeystone, EventMineKeystone{Keystone: ks})

btcHeight, err := m.bitcoinHeight(ctx)
if err != nil {
return fmt.Errorf("get Bitcoin height: %w", err)
Expand Down Expand Up @@ -394,6 +399,9 @@ func (m *Miner) mineKeystone(ctx context.Context, ks *hemi.L2Keystone) error {

log.Infof("Successfully broadcast PoP transaction to Bitcoin with TX hash %v", txHash)

go m.dispatchEvent(EventTypeTransactionBroadcast,
EventTransactionBroadcast{Keystone: ks, TxHash: txHash.String()})

return nil
}

Expand Down
22 changes: 22 additions & 0 deletions web/packages/pop-miner/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,25 @@ export const bitcoinUTXOs: typeof types.bitcoinUTXOs = ({ scriptHash }) => {
scriptHash: scriptHash,
}) as Promise<BitcoinUTXOsResult>;
};

export const addEventListener: typeof types.addEventListener = (
eventType,
listener,
) => {
return dispatchVoid({
method: 'addEventListener',
eventType: eventType,
listener: listener,
});
};

export const removeEventListener: typeof types.addEventListener = (
eventType,
listener,
) => {
return dispatchVoid({
method: 'removeEventListener',
eventType: eventType,
listener: listener,
});
};
8 changes: 5 additions & 3 deletions web/packages/pop-miner/src/browser/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ export type Method =
| 'l2Keystones'
| 'bitcoinBalance'
| 'bitcoinInfo'
| 'bitcoinUTXOs';
| 'bitcoinUTXOs'
| 'addEventListener'
| 'removeEventListener';

/**
* Dispatch args.
*
* @see dispatch
*/
export type DispatchArgs = Record<string, unknown> & {
export type DispatchArgs = Record<string, any> & {
/**
* The method to be dispatched.
*
Expand Down Expand Up @@ -66,7 +68,7 @@ export const init: typeof types.init = async ({ wasmURL }) => {
loadPromise = loadWASM({ wasmURL }).catch((err) => {
loadPromise = undefined;
throw err;
});
}) as Promise<WASM>;
}

globalWASM = globalWASM || (await loadPromise);
Expand Down
94 changes: 94 additions & 0 deletions web/packages/pop-miner/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,100 @@ export declare function bitcoinAddressToScriptHash(
args: BitcoinAddressToScriptHashArgs,
): Promise<BitcoinAddressToScriptHashResult>;

/**
* Represents a type of event.
*/
export type EventType =
| 'minerStart'
| 'minerStop'
| 'mineKeystone'
| 'transactionBroadcast';

/**
* An event that has been dispatched.
*/
export type Event = {
type: EventType;
};

/**
* Dispatched when the PoP miner has stopped.
*/
export type EventMinerStart = Event & {
type: 'minerStart';
};

/**
* Dispatched when the PoP miner has exited.
*/
export type EventMinerStop = Event & {
type: 'minerStop';

/**
* The error that caused the PoP miner to exit, or null.
*/
error?: Error;
};

/**
* Dispatched when the PoP miner begins mining a keystone.
*/
export type EventMineKeystone = Event & {
type: 'mineKeystone';

/**
* The keystone to be mined.
*/
keystone: L2Keystone;
};

/**
* Dispatched when the PoP miner broadcasts a PoP transaction to the Bitcoin
* network.
*/
export type EventTransactionBroadcast = Event & {
type: 'transactionBroadcast';

/**
* The keystone that was mined.
*/
keystone: L2Keystone;

/**
* The hash of the Bitcoin transaction.
*/
txHash: string;
};

/**
* An event listener that can receive events.
*/
export interface EventListener {
(event: Event): void;
}

/**
* Registers an event listener.
*
* @param eventType The event type to listen for. If '*' then listen for all events.
* @param listener The event listener that will be called when the event is dispatched.
*/
export declare function addEventListener(
eventType: EventType | '*',
listener: EventListener,
): Promise<void>;

/**
* Unregisters an event listener.
*
* @param eventType The event type to stop listening for.
* @param listener The event listener to unregister.
*/
export declare function removeEventListener(
eventType: EventType | '*',
listener: EventListener,
): Promise<void>;

/**
* @see startPoPMiner
*/
Expand Down
72 changes: 71 additions & 1 deletion web/popminer/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

package main

import "syscall/js"
import (
"syscall/js"

"github.com/hemilabs/heminetwork/service/popm"
)

// Method represents a method that can be dispatched.
type Method string
Expand All @@ -26,6 +30,10 @@ const (
MethodBitcoinBalance Method = "bitcoinBalance" // Retrieve bitcoin balance
MethodBitcoinInfo Method = "bitcoinInfo" // Retrieve bitcoin information
MethodBitcoinUTXOs Method = "bitcoinUTXOs" // Retrieve bitcoin UTXOs

// Events
MethodEventListenerAdd Method = "addEventListener" // Register event listener
MethodEventListenerRemove Method = "removeEventListener" // Unregister event listener
)

// ErrorCode is used to differentiate between error types.
Expand Down Expand Up @@ -206,3 +214,65 @@ type BitcoinUTXO struct {
// Value is the value of the output in satoshis.
Value int64 `json:"value"`
}

// EventType represents a type of event.
type EventType string

const (
// EventTypeMinerStart is dispatched when the PoP miner has started.
EventTypeMinerStart EventType = "minerStart"

// EventTypeMinerStop is dispatched when the PoP miner has exited.
EventTypeMinerStop EventType = "minerStop"

// EventTypeMineKeystone is dispatched when the PoP miner is mining an L2
// keystone.
EventTypeMineKeystone EventType = "mineKeystone"

// EventTypeTransactionBroadcast is dispatched when the PoP miner has
// broadcast a Bitcoin transaction to the network.
EventTypeTransactionBroadcast EventType = "transactionBroadcast"
)

// popmEvents contains events dispatched by the native PoP Miner.
// These events will be forwarded to JavaScript, however we also dispatch events
// that are specific to the WebAssembly PoP Miner.
var popmEvents = map[popm.EventType]EventType{
popm.EventTypeMineKeystone: EventTypeMineKeystone,
popm.EventTypeTransactionBroadcast: EventTypeTransactionBroadcast,
}

// eventTypes is a map used to parse string event types.
var eventTypes = map[string]EventType{
"*": "*", // Listen for all events.
EventTypeMinerStart.String(): EventTypeMinerStart,
EventTypeMinerStop.String(): EventTypeMinerStop,
EventTypeMineKeystone.String(): EventTypeMineKeystone,
EventTypeTransactionBroadcast.String(): EventTypeTransactionBroadcast,
}

// String returns the string representation of the event type.
func (e EventType) String() string {
return string(e)
}

// MarshalJS returns the JavaScript representation of the event type.
func (e EventType) MarshalJS() (js.Value, error) {
return jsValueOf(e.String()), nil
}

// EventMinerStop is the data for EventTypeMinerStop.
type EventMinerStop struct {
Error *Error `json:"error"`
}

// EventMineKeystone is the data for EventTypeMineKeystone.
type EventMineKeystone struct {
Keystone L2Keystone `json:"keystone"`
}

// EventTransactionBroadcast is the data for EventTypeTransactionBroadcast.
type EventTransactionBroadcast struct {
Keystone L2Keystone `json:"keystone"`
TxHash string `json:"txHash"`
}
Loading

0 comments on commit 9c4cea5

Please sign in to comment.