Files
ark/client/utils/state.go
Louis Singer 0fb34cb13d Dynamic min-relay-fee and dust amount (#280)
* [btc-embedded] add chainfee.Estimator and extraAPI interfaces

* dynamic fee amount

* dynamic dust amount

* [client] fix linter errors

* [domain] fix unit tests

* [server] return dust amount in GetInfo RPC

* [sdk] fix lnd dependencie

* go work sync

* fix witness stack size forfeit tx size estimator

* remove hardcoded fee values in covenant txbuilder

* lower liquid feerate

* fix after reviews

* go work sync
2024-09-10 17:22:09 +02:00

305 lines
6.4 KiB
Go

package utils
import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/ark-network/ark/common"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/urfave/cli/v2"
)
const (
ASP_URL = "asp_url"
ASP_PUBKEY = "asp_public_key"
ROUND_LIFETIME = "round_lifetime"
UNILATERAL_EXIT_DELAY = "unilateral_exit_delay"
BOARDING_TEMPLATE = "boarding_template"
ENCRYPTED_PRVKEY = "encrypted_private_key"
PASSWORD_HASH = "password_hash"
PUBKEY = "public_key"
NETWORK = "network"
EXPLORER = "explorer"
DUST = "dust"
defaultNetwork = "liquid"
state_file = "state.json"
)
var initialState = map[string]string{
ASP_URL: "",
ASP_PUBKEY: "",
ROUND_LIFETIME: "",
UNILATERAL_EXIT_DELAY: "",
ENCRYPTED_PRVKEY: "",
PASSWORD_HASH: "",
PUBKEY: "",
DUST: "546",
NETWORK: defaultNetwork,
}
func GetNetwork(ctx *cli.Context) (*common.Network, error) {
state, err := GetState(ctx)
if err != nil {
return nil, err
}
net, ok := state[NETWORK]
if !ok {
return nil, fmt.Errorf("network not found in state")
}
return networkFromString(net), nil
}
func GetRoundLifetime(ctx *cli.Context) (int64, error) {
state, err := GetState(ctx)
if err != nil {
return -1, err
}
lifetime := state[ROUND_LIFETIME]
if len(lifetime) <= 0 {
return -1, fmt.Errorf("missing round lifetime")
}
roundLifetime, err := strconv.Atoi(lifetime)
if err != nil {
return -1, err
}
return int64(roundLifetime), nil
}
func GetUnilateralExitDelay(ctx *cli.Context) (int64, error) {
state, err := GetState(ctx)
if err != nil {
return -1, err
}
delay := state[UNILATERAL_EXIT_DELAY]
if len(delay) <= 0 {
return -1, fmt.Errorf("missing unilateral exit delay")
}
redeemDelay, err := strconv.Atoi(delay)
if err != nil {
return -1, err
}
return int64(redeemDelay), nil
}
func GetBoardingDescriptor(ctx *cli.Context) (string, error) {
state, err := GetState(ctx)
if err != nil {
return "", err
}
pubkey, err := GetWalletPublicKey(ctx)
if err != nil {
return "", err
}
template := state[BOARDING_TEMPLATE]
if len(template) <= 0 {
return "", fmt.Errorf("missing boarding descriptor template")
}
pubkeyhex := hex.EncodeToString(schnorr.SerializePubKey(pubkey))
return strings.ReplaceAll(template, "USER", pubkeyhex), nil
}
func GetWalletPublicKey(ctx *cli.Context) (*secp256k1.PublicKey, error) {
state, err := GetState(ctx)
if err != nil {
return nil, err
}
publicKeyString := state[PUBKEY]
if len(publicKeyString) <= 0 {
return nil, fmt.Errorf("missing public key")
}
publicKeyBytes, err := hex.DecodeString(publicKeyString)
if err != nil {
return nil, err
}
return secp256k1.ParsePubKey(publicKeyBytes)
}
func GetAspPublicKey(ctx *cli.Context) (*secp256k1.PublicKey, error) {
state, err := GetState(ctx)
if err != nil {
return nil, err
}
arkPubKey := state[ASP_PUBKEY]
if len(arkPubKey) <= 0 {
return nil, fmt.Errorf("missing asp public key")
}
pubKeyBytes, err := hex.DecodeString(arkPubKey)
if err != nil {
return nil, err
}
return secp256k1.ParsePubKey(pubKeyBytes)
}
func GetDust(ctx *cli.Context) (uint64, error) {
state, err := GetState(ctx)
if err != nil {
return 0, err
}
dust := state[DUST]
if len(dust) <= 0 {
return 0, fmt.Errorf("missing dust")
}
dustAmount, err := strconv.Atoi(dust)
if err != nil {
return 0, err
}
return uint64(dustAmount), nil
}
func GetState(ctx *cli.Context) (map[string]string, error) {
datadir := ctx.String("datadir")
stateFilePath := filepath.Join(datadir, state_file)
file, err := os.ReadFile(stateFilePath)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
if err := setInitialState(stateFilePath); err != nil {
return nil, err
}
return initialState, nil
}
data := map[string]string{}
if err := json.Unmarshal(file, &data); err != nil {
return nil, err
}
return data, nil
}
func PrivateKeyFromPassword(ctx *cli.Context) (*secp256k1.PrivateKey, error) {
state, err := GetState(ctx)
if err != nil {
return nil, err
}
encryptedPrivateKeyString := state[ENCRYPTED_PRVKEY]
if len(encryptedPrivateKeyString) <= 0 {
return nil, fmt.Errorf("missing encrypted private key")
}
encryptedPrivateKey, err := hex.DecodeString(encryptedPrivateKeyString)
if err != nil {
return nil, fmt.Errorf("invalid encrypted private key: %s", err)
}
password, err := ReadPassword(ctx, true)
if err != nil {
return nil, err
}
fmt.Println("wallet unlocked")
cypher := NewAES128Cypher()
privateKeyBytes, err := cypher.decrypt(encryptedPrivateKey, password)
if err != nil {
return nil, err
}
privateKey := secp256k1.PrivKeyFromBytes(privateKeyBytes)
return privateKey, nil
}
func SetState(ctx *cli.Context, data map[string]string) error {
currentData, err := GetState(ctx)
if err != nil {
return err
}
mergedData := merge(currentData, data)
jsonString, err := json.Marshal(mergedData)
if err != nil {
return err
}
datadir := ctx.String("datadir")
statePath := filepath.Join(datadir, state_file)
err = os.WriteFile(statePath, jsonString, 0755)
if err != nil {
return fmt.Errorf("writing to file: %w", err)
}
return nil
}
func networkFromString(net string) *common.Network {
switch net {
case common.Liquid.Name:
return &common.Liquid
case common.LiquidTestNet.Name:
return &common.LiquidTestNet
case common.LiquidRegTest.Name:
return &common.LiquidRegTest
case common.Bitcoin.Name:
return &common.Bitcoin
case common.BitcoinTestNet.Name:
return &common.BitcoinTestNet
case common.BitcoinRegTest.Name:
return &common.BitcoinRegTest
case common.BitcoinSigNet.Name:
return &common.BitcoinSigNet
default:
panic(fmt.Sprintf("unknown network (%s)", net))
}
}
func setInitialState(stateFilePath string) error {
jsonString, err := json.Marshal(initialState)
if err != nil {
return err
}
return os.WriteFile(stateFilePath, jsonString, 0755)
}
func getBaseURL(ctx *cli.Context) (string, error) {
state, err := GetState(ctx)
if err != nil {
return "", err
}
baseURL := state[EXPLORER]
if len(baseURL) <= 0 {
return "", fmt.Errorf("missing explorer base url")
}
return baseURL, nil
}
func merge(maps ...map[string]string) map[string]string {
merge := make(map[string]string, 0)
for _, m := range maps {
for k, v := range m {
merge[k] = v
}
}
return merge
}