220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
// Copyright 2020 Coinbase, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package configuration
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/coinbase/rosetta-bitcoin/bitcoin"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/coinbase/rosetta-sdk-go/storage/encoder"
|
|
"github.com/coinbase/rosetta-sdk-go/types"
|
|
)
|
|
|
|
// Mode is the setting that determines if
|
|
// the implementation is "online" or "offline".
|
|
type Mode string
|
|
|
|
const (
|
|
// Online is when the implementation is permitted
|
|
// to make outbound connections.
|
|
Online Mode = "ONLINE"
|
|
|
|
// Offline is when the implementation is not permitted
|
|
// to make outbound connections.
|
|
Offline Mode = "OFFLINE"
|
|
|
|
// Mainnet is the Bitcoin Mainnet.
|
|
Mainnet string = "MAINNET"
|
|
|
|
// Testnet is Bitcoin Testnet3.
|
|
Testnet string = "TESTNET"
|
|
|
|
// mainnetConfigPath is the path of the Bitcoin
|
|
// configuration file for mainnet.
|
|
mainnetConfigPath = "/app/bitcoin-mainnet.conf"
|
|
|
|
// testnetConfigPath is the path of the Bitcoin
|
|
// configuration file for testnet.
|
|
testnetConfigPath = "/app/bitcoin-testnet.conf"
|
|
|
|
// Zstandard compression dictionaries
|
|
transactionNamespace = "transaction"
|
|
testnetTransactionDictionary = "/app/testnet-transaction.zstd"
|
|
mainnetTransactionDictionary = "/app/mainnet-transaction.zstd"
|
|
|
|
mainnetRPCPort = 8332
|
|
testnetRPCPort = 18332
|
|
|
|
// min prune depth is 288:
|
|
// https://github.com/bitcoin/bitcoin/blob/ad2952d17a2af419a04256b10b53c7377f826a27/src/validation.h#L84
|
|
pruneDepth = int64(10000) //nolint
|
|
|
|
// min prune height (on mainnet):
|
|
// https://github.com/bitcoin/bitcoin/blob/62d137ac3b701aae36c1aa3aa93a83fd6357fde6/src/chainparams.cpp#L102
|
|
minPruneHeight = int64(100000) //nolint
|
|
|
|
// attempt to prune once an hour
|
|
pruneFrequency = 60 * time.Minute
|
|
|
|
// DataDirectory is the default location for all
|
|
// persistent data.
|
|
DataDirectory = "/data"
|
|
|
|
bitcoindPath = "bitcoind"
|
|
indexerPath = "indexer"
|
|
|
|
// allFilePermissions specifies anyone can do anything
|
|
// to the file.
|
|
allFilePermissions = 0777
|
|
|
|
// ModeEnv is the environment variable read
|
|
// to determine mode.
|
|
ModeEnv = "MODE"
|
|
|
|
// NetworkEnv is the environment variable
|
|
// read to determine network.
|
|
NetworkEnv = "NETWORK"
|
|
|
|
// PortEnv is the environment variable
|
|
// read to determine the port for the Rosetta
|
|
// implementation.
|
|
PortEnv = "PORT"
|
|
)
|
|
|
|
// PruningConfiguration is the configuration to
|
|
// use for pruning in the indexer.
|
|
type PruningConfiguration struct {
|
|
Frequency time.Duration
|
|
Depth int64
|
|
MinHeight int64
|
|
}
|
|
|
|
// Configuration determines how
|
|
type Configuration struct {
|
|
Mode Mode
|
|
Network *types.NetworkIdentifier
|
|
Params *chaincfg.Params
|
|
Currency *types.Currency
|
|
GenesisBlockIdentifier *types.BlockIdentifier
|
|
Port int
|
|
RPCPort int
|
|
ConfigPath string
|
|
Pruning *PruningConfiguration
|
|
IndexerPath string
|
|
BitcoindPath string
|
|
Compressors []*encoder.CompressorEntry
|
|
}
|
|
|
|
// LoadConfiguration attempts to create a new Configuration
|
|
// using the ENVs in the environment.
|
|
func LoadConfiguration(baseDirectory string) (*Configuration, error) {
|
|
config := &Configuration{}
|
|
config.Pruning = &PruningConfiguration{
|
|
Frequency: pruneFrequency,
|
|
Depth: pruneDepth,
|
|
MinHeight: minPruneHeight,
|
|
}
|
|
|
|
modeValue := Mode(os.Getenv(ModeEnv))
|
|
switch modeValue {
|
|
case Online:
|
|
config.Mode = Online
|
|
config.IndexerPath = path.Join(baseDirectory, indexerPath)
|
|
if err := ensurePathExists(config.IndexerPath); err != nil {
|
|
return nil, fmt.Errorf("%w: unable to create indexer path", err)
|
|
}
|
|
|
|
config.BitcoindPath = path.Join(baseDirectory, bitcoindPath)
|
|
if err := ensurePathExists(config.BitcoindPath); err != nil {
|
|
return nil, fmt.Errorf("%w: unable to create bitcoind path", err)
|
|
}
|
|
case Offline:
|
|
config.Mode = Offline
|
|
case "":
|
|
return nil, errors.New("MODE must be populated")
|
|
default:
|
|
return nil, fmt.Errorf("%s is not a valid mode", modeValue)
|
|
}
|
|
|
|
networkValue := os.Getenv(NetworkEnv)
|
|
switch networkValue {
|
|
case Mainnet:
|
|
config.Network = &types.NetworkIdentifier{
|
|
Blockchain: bitcoin.Blockchain,
|
|
Network: bitcoin.MainnetNetwork,
|
|
}
|
|
config.GenesisBlockIdentifier = bitcoin.MainnetGenesisBlockIdentifier
|
|
config.Params = bitcoin.MainnetParams
|
|
config.Currency = bitcoin.MainnetCurrency
|
|
config.ConfigPath = mainnetConfigPath
|
|
config.RPCPort = mainnetRPCPort
|
|
config.Compressors = []*encoder.CompressorEntry{
|
|
{
|
|
Namespace: transactionNamespace,
|
|
DictionaryPath: mainnetTransactionDictionary,
|
|
},
|
|
}
|
|
case Testnet:
|
|
config.Network = &types.NetworkIdentifier{
|
|
Blockchain: bitcoin.Blockchain,
|
|
Network: bitcoin.TestnetNetwork,
|
|
}
|
|
config.GenesisBlockIdentifier = bitcoin.TestnetGenesisBlockIdentifier
|
|
config.Params = bitcoin.TestnetParams
|
|
config.Currency = bitcoin.TestnetCurrency
|
|
config.ConfigPath = testnetConfigPath
|
|
config.RPCPort = testnetRPCPort
|
|
config.Compressors = []*encoder.CompressorEntry{
|
|
{
|
|
Namespace: transactionNamespace,
|
|
DictionaryPath: testnetTransactionDictionary,
|
|
},
|
|
}
|
|
case "":
|
|
return nil, errors.New("NETWORK must be populated")
|
|
default:
|
|
return nil, fmt.Errorf("%s is not a valid network", networkValue)
|
|
}
|
|
|
|
portValue := os.Getenv(PortEnv)
|
|
if len(portValue) == 0 {
|
|
return nil, errors.New("PORT must be populated")
|
|
}
|
|
|
|
port, err := strconv.Atoi(portValue)
|
|
if err != nil || len(portValue) == 0 || port <= 0 {
|
|
return nil, fmt.Errorf("%w: unable to parse port %s", err, portValue)
|
|
}
|
|
config.Port = port
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// ensurePathsExist directories along
|
|
// a path if they do not exist.
|
|
func ensurePathExists(path string) error {
|
|
if err := os.MkdirAll(path, os.FileMode(allFilePermissions)); err != nil {
|
|
return fmt.Errorf("%w: unable to create %s directory", err, path)
|
|
}
|
|
|
|
return nil
|
|
}
|