Skip to content

Commit

Permalink
popm/wasm: add error codes
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuasing committed Jun 21, 2024
1 parent a4685a5 commit 492952b
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 68 deletions.
16 changes: 16 additions & 0 deletions web/popminer/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,24 @@ const (
MethodBitcoinUTXOs Method = "bitcoinUTXOs" // Retrieve bitcoin UTXOs
)

// ErrorCode is a unique identifier used to differentiate between error types.
type ErrorCode string

const (
// ErrorCodeInternal is used when the error is internal, either due to an
// invalid dispatch or a panic.
ErrorCodeInternal ErrorCode = "internal"

// ErrorCodeInvalidValue is used when an invalid value was provided for
// a dispatch argument.
ErrorCodeInvalidValue ErrorCode = "invalid-value"
)

// Error represents an error that has occurred within the WASM PoP Miner.
type Error struct {
// Code is a unique identifier used to differentiate between error types.
Code ErrorCode `json:"code,omitempty"`

// Message is the error message.
Message string `json:"message"`

Expand Down
26 changes: 14 additions & 12 deletions web/popminer/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ func dispatch(this js.Value, args []js.Value) any {
if r := recover(); r != nil {
log.Criticalf("recovered panic: %v", r)
log.Criticalf(string(debug.Stack()))
reject.Invoke(jsError(fmt.Errorf("recovered panic: %v", r)))
reject.Invoke(jsErrorWithCode(ErrorCodeInternal,
fmt.Errorf("recovered panic: %v", r)))
}
}()

Expand Down Expand Up @@ -194,22 +195,23 @@ func generateKey(_ js.Value, args []js.Value) (any, error) {
btcChainParams = &btcchaincfg.MainNetParams
netNormalized = "mainnet"
default:
return js.Null(), fmt.Errorf("invalid network: %v", net)
return nil, errorWithCode(ErrorCodeInvalidValue,
fmt.Errorf("invalid network: %s", net))
}

// TODO(joshuasing): consider alternative as dcrsecpk256k1 package is large.
privKey, err := dcrsecpk256k1.GeneratePrivateKey()
if err != nil {
log.Errorf("failed to generate private key: %v", err)
return js.Null(), fmt.Errorf("generate secp256k1 private key: %w", err)
return nil, fmt.Errorf("generate secp256k1 private key: %w", err)
}
btcAddress, err := btcutil.NewAddressPubKey(
privKey.PubKey().SerializeCompressed(),
btcChainParams,
)
if err != nil {
log.Errorf("failed to generate btc address: %v", err)
return js.Null(), fmt.Errorf("create BTC address from public key: %v", err)
return nil, fmt.Errorf("create BTC address from public key: %v", err)
}

compressedPubKey := privKey.PubKey().SerializeCompressed()
Expand Down Expand Up @@ -310,11 +312,11 @@ func ping(_ js.Value, _ []js.Value) (any, error) {

activePM, err := activeMiner()
if err != nil {
return js.Null(), err
return nil, err
}
pr, err := activePM.miner.Ping(activePM.ctx, time.Now().Unix())
if err != nil {
return js.Null(), err
return nil, err
}

// TODO(joshuasing): protocol.PingResponse should really use a more accurate
Expand All @@ -337,11 +339,11 @@ func l2Keystones(_ js.Value, args []js.Value) (any, error) {

activePM, err := activeMiner()
if err != nil {
return js.Null(), err
return nil, err
}
pr, err := activePM.miner.L2Keystones(activePM.ctx, count)
if err != nil {
return js.Null(), err
return nil, err
}

keystones := make([]L2Keystone, len(pr.L2Keystones))
Expand Down Expand Up @@ -370,11 +372,11 @@ func bitcoinBalance(_ js.Value, args []js.Value) (any, error) {

activePM, err := activeMiner()
if err != nil {
return js.Null(), err
return nil, err
}
pr, err := activePM.miner.BitcoinBalance(activePM.ctx, scriptHash)
if err != nil {
return js.Null(), err
return nil, err
}

return BitcoinBalanceResult{
Expand All @@ -389,11 +391,11 @@ func bitcoinInfo(_ js.Value, _ []js.Value) (any, error) {

activePM, err := activeMiner()
if err != nil {
return js.Null(), err
return nil, err
}
pr, err := activePM.miner.BitcoinInfo(activePM.ctx)
if err != nil {
return js.Null(), err
return nil, err
}

return BitcoinInfoResult{
Expand Down
66 changes: 60 additions & 6 deletions web/popminer/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package main

import (
"errors"
"reflect"
"runtime/debug"
"strings"
Expand Down Expand Up @@ -183,18 +184,71 @@ func jsValueSafe(v any) (jsv js.Value) {
jsv = js.Undefined()
}
}()

// Special handling
switch x := v.(type) {
case ErrorCode:
return js.ValueOf(string(x))
}

return js.ValueOf(v)
}

// jsError returns a [js.Value] representing the given error.
// codedError represents an error that has a related [ErrorCode].
type codedError struct {
code ErrorCode
err error
}

// errorWithCode returns an error containing the given error code.
func errorWithCode(code ErrorCode, err error) error {
return codedError{
code: code,
err: err,
}
}

// codeFromError returns the error code from the error, if possible, otherwise
// ErrorCodeInternal will be returned.
func codeFromError(err error) ErrorCode {
var ce codedError
if errors.As(err, &ce) {
return ce.code
}
return ErrorCodeInternal
}

// Error returns the error string.
func (c codedError) Error() string {
return c.err.Error()
}

// Unwrap returns the wrapped error.
func (c codedError) Unwrap() error {
return c.err
}

// jsError returns a [js.Value] representing the given error. The error code
// will be extracted from the given error using codeFromError, if available,
// otherwise the error code will be ErrorCodeInternal.
func jsError(err error) js.Value {
log.Tracef("jsError: %v", err)
defer log.Tracef("jsError exit")
return newJSError(codeFromError(err), err.Error())
}

// jsErrorWithCode returns a [js.Value] representing the given error with
// an error code.
func jsErrorWithCode(code ErrorCode, err error) js.Value {
return newJSError(code, err.Error())
}

stack := string(debug.Stack())
// newJSError returns a new [js.Value] for an [Error] with the given code and
// message. The stack will be generated, skipping stackSkip callers. The
// timestamp will be set to time.Now() in Unix seconds.
func newJSError(code ErrorCode, message string) js.Value {
return jsValueOf(Error{
Message: err.Error(),
Stack: stack,
Code: code,
Message: message,
Stack: string(debug.Stack()),
Timestamp: time.Now().Unix(),
})
}
62 changes: 30 additions & 32 deletions web/www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,25 @@
<head>
<meta charset="UTF-8" />
<title>PoP miner integration tests</title>
</head>

<body>
<script src="wasm_exec.js"></script>
<script src="popminer.js"></script>

<p>
</head>
<body>
<div>
<button id="VersionButton">version</button>
<span class="VersionShow"></span>
</p>
<pre class="VersionShow"></pre>
</div>

<p>
<div>
<input id="GenerateKeyNetworkInput" value="testnet3">
<label for="GenerateKeyNetworkInput">bitcoin network</label>
<br>

<button id="GenerateKeyButton">generate key</button>
<span class="GenerateKeyShow"></span>
</p>
<pre class="GenerateKeyShow"></pre>
</div>

<p>
<div>
<input id="StartPopMinerLogLevelInput" value="hemiwasm=INFO:popm=INFO:protocol=INFO">
<label for="StartPopMinerLogLevelInput">log level</label>
<br>
Expand All @@ -45,55 +43,55 @@
<br>

<button id="StartPopMinerButton">Run PoP Miner</button>
<span class="StartPopMinerShow"></span>
</p>
<pre class="StartPopMinerShow"></pre>
</div>

<p>
<div>
<button id="StopPopMinerButton">Stop PoP Miner</button>
<span class="StopPopMinerShow"></span>
</p>
<pre class="StopPopMinerShow"></pre>
</div>

<!-- connected commands go here, run after StartPopMinerButton -->
<hr>
<div>The following commands require a live connection (after StartPopMinerButton)</div>
<p>The following commands require a live connection (after StartPopMinerButton)</p>
<br>

<p>
<div>
<button id="PingButton">ping</button>
<span class="PingShow"></span>
</p>
<pre class="PingShow"></pre>
</div>

<p>
<div>
<input id="L2KeystonesNumL2KeystonesInput" value="2" type="number">
<label for="L2KeystonesNumL2KeystonesInput">num l2 keystones</label>
<br>

<button id="L2KeystonesButton">l2 keystones</button>
<span class="L2KeystonesShow"></span>
</p>
<pre class="L2KeystonesShow"></pre>
</div>

<p>
<div>
<input id="BitcoinBalanceScriptHashInput" value="xxx">
<label for="BitcoinBalanceScriptHashInput">script hash</label>
<br>

<button id="BitcoinBalanceButton">bitcoin balance</button>
<span class="BitcoinBalanceShow"></span>
</p>
<pre class="BitcoinBalanceShow"></pre>
</div>

<p>
<div>
<button id="BitcoinInfoButton">bitcoin info</button>
<span class="BitcoinInfoShow"></span>
</p>
<pre class="BitcoinInfoShow"></pre>
</div>

<p>
<div>
<input id="BitcoinUTXOsScriptHashInput" value="xxx">
<label for="BitcoinUTXOsScriptHashInput">script hash</label>
<br>

<button id="BitcoinUTXOsButton">bitcoin utxos</button>
<span class="BitcoinUTXOsShow"></span>
</p>
<pre class="BitcoinUTXOsShow"></pre>
</div>

<script src="index.js" defer></script>
</body>
Expand Down
Loading

0 comments on commit 492952b

Please sign in to comment.