├── .gitignore ├── Makefile ├── README.md ├── cmd └── pscli │ ├── README.md │ ├── commands.go │ └── main.go ├── common ├── conversion.go ├── log.go └── utils.go ├── config.go ├── connectors ├── daemons │ ├── bitcoind │ │ ├── client.go │ │ ├── client_test.go │ │ ├── size.go │ │ ├── tx.go │ │ ├── tx_test.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── bitcoind_simple │ │ ├── connector.go │ │ ├── storage.go │ │ └── utils.go │ ├── geth │ │ ├── client.go │ │ ├── client_test.go │ │ ├── errors.go │ │ ├── extension.go │ │ ├── storage.go │ │ └── utils.go │ └── lnd │ │ ├── lnd.go │ │ ├── log.go │ │ └── utils.go ├── interface.go ├── payment.go ├── payment_details.go ├── payment_details_test.go ├── rpc │ ├── bitcoin │ │ ├── client.go │ │ ├── params.go │ │ ├── validation.go │ │ └── validation_test.go │ ├── bitcoincash │ │ ├── client.go │ │ ├── params.go │ │ ├── validation.go │ │ └── validation_test.go │ ├── dash │ │ ├── client.go │ │ ├── params.go │ │ ├── validation.go │ │ └── validation_test.go │ ├── ethereum │ │ ├── validation.go │ │ └── validation_test.go │ ├── interface.go │ ├── litecoin │ │ ├── client.go │ │ ├── params.go │ │ ├── validation.go │ │ └── validation_test.go │ └── log.go └── storage.go ├── crpc ├── README.md ├── errors.go ├── generate.sh ├── log.go ├── rpc.pb.go ├── rpc.proto ├── server.go └── utils.go ├── db ├── inmemory │ └── payments.go └── sqlite │ ├── bitcoin_simnet_state.go │ ├── connector_state.go │ ├── connector_state_test.go │ ├── db.go │ ├── geth_accounts.go │ ├── geth_accounts_test.go │ ├── log.go │ ├── migrations.go │ ├── migrations_test.go │ ├── payment.go │ ├── payment_test.go │ ├── test_db_add_status_field │ └── utils.go ├── docker ├── mainnet │ ├── .env.empty │ ├── .gitignore │ ├── bitcoin-cash │ │ ├── Dockerfile │ │ ├── bitcoin-cash.mainnet.conf │ │ └── entrypoint.sh │ ├── bitcoin-lightning │ │ ├── Dockerfile │ │ ├── bitcoin-lightning.mainnet.conf │ │ └── entrypoint.sh │ ├── bitcoin-neutrino │ │ ├── Dockerfile │ │ ├── bitcoin-neutrino.mainnet.conf │ │ └── entrypoint.sh │ ├── bitcoin │ │ ├── Dockerfile │ │ ├── bitcoin.mainnet.conf │ │ └── entrypoint.sh │ ├── connector │ │ ├── Dockerfile │ │ ├── connector.mainnet.conf │ │ └── entrypoint.sh │ ├── dash │ │ ├── Dockerfile │ │ ├── dash.mainnet.conf │ │ └── entrypoint.sh │ ├── docker-compose.yml │ ├── ethereum │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ └── ethereum.mainnet.conf │ ├── fluentd │ │ ├── Dockerfile │ │ └── fluent.conf │ ├── litecoin │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ └── litecoin.mainnet.conf │ ├── logrotate.conf │ └── rsyslog.conf ├── simnet │ ├── .env │ ├── bitcoin-cash │ │ ├── Dockerfile │ │ ├── bitcoin.simnet.primary.conf │ │ ├── bitcoin.simnet.secondary.conf │ │ └── entrypoint.sh │ ├── bitcoin-lightning-helper │ │ ├── Dockerfile │ │ └── http-server.py │ ├── bitcoin-lightning │ │ ├── Dockerfile │ │ ├── bitcoin-lightning.simnet.primary.conf │ │ ├── bitcoin-lightning.simnet.secondary.conf │ │ └── entrypoint.sh │ ├── bitcoin │ │ ├── Dockerfile │ │ ├── bitcoin.simnet.primary.conf │ │ ├── bitcoin.simnet.secondary.conf │ │ └── entrypoint.sh │ ├── blocks-generator │ │ ├── Dockerfile │ │ └── entrypoint.sh │ ├── connector │ │ ├── Dockerfile │ │ ├── connector.simnet.conf │ │ └── entrypoint.sh │ ├── dash │ │ ├── Dockerfile │ │ ├── dash.simnet.primary.conf │ │ ├── dash.simnet.secondary.conf │ │ └── entrypoint.sh │ ├── docker-compose.yml │ ├── ethereum-bootnode │ │ ├── Dockerfile │ │ └── entrypoint.sh │ ├── ethereum │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ ├── ethereum.simnet.primary.conf │ │ ├── ethereum.simnet.secondary.conf │ │ └── genesis.json │ ├── init.pl │ ├── litecoin │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ ├── litecoin.simnet.primary.conf │ │ └── litecoin.simnet.secondary.conf │ ├── logrotate.conf │ └── rsyslog.conf └── testnet │ ├── .env.empty │ ├── .gitignore │ ├── bitcoin-cash │ ├── Dockerfile │ ├── bitcoin-cash.testnet.conf │ └── entrypoint.sh │ ├── bitcoin-lightning │ ├── Dockerfile │ ├── bitcoin-lightning.testnet.conf │ └── entrypoint.sh │ ├── bitcoin-neutrino │ ├── Dockerfile │ ├── bitcoin-neutrino.testnet.conf │ └── entrypoint.sh │ ├── bitcoin │ ├── Dockerfile │ ├── bitcoin.testnet.conf │ └── entrypoint.sh │ ├── connector │ ├── Dockerfile │ ├── connector.testnet.conf │ └── entrypoint.sh │ ├── dash │ ├── Dockerfile │ ├── dash.testnet.conf │ └── entrypoint.sh │ ├── docker-compose.yml │ ├── ethereum │ ├── Dockerfile │ ├── entrypoint.sh │ └── ethereum.testnet.conf │ ├── litecoin │ ├── Dockerfile │ ├── entrypoint.sh │ └── litecoin.testnet.conf │ ├── logrotate.conf │ └── rsyslog.conf ├── go.mod ├── go.sum ├── log.go ├── main.go ├── metrics ├── crypto │ ├── backend.go │ └── metric.go ├── labels.go ├── log.go ├── rpc │ └── backend.go └── server.go ├── signal.go ├── utils.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | vendor/ 17 | .idea 18 | 19 | ./connector 20 | connector.conf 21 | 22 | docker/**/bin 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pay server - is the blockchain microservice which is working on [zigzag.io](https://zigzag.io). It is used as unified API for other microservices to receive and send cryptocurrency. 2 | 3 | | State | Feature | 4 | | ------------- | ------------- | 5 | | implemented | Unify payment API for BTC, LTC, DASH, ETH, BCH, and Lightning Network | 6 | | implemented | Report health statistics about internal state of synchronisation, fees, request delays, sent and received volume, amount of fees spent on payments | 7 | | not implemented | Payment re-try in case of failure | 8 | | not implemented | UTXO re-orginisation | 9 | | not implemented | Lightning Network channel re-balancing | 10 | |not implemented|Support of payments on HTLC addresses| 11 | 12 | ``` 13 | GRPC API: 14 | 15 | // CreateReceipt is used to create blockchain deposit address in 16 | // case of blockchain media, and lightning network invoice in 17 | // case of the lightning media, which will be used to receive money from 18 | // external entity. 19 | rpc CreateReceipt (CreateReceiptRequest) returns (CreateReceiptResponse); 20 | 21 | // ValidateReceipt is used to validate receipt for given asset and media. 22 | rpc ValidateReceipt (ValidateReceiptRequest) returns (EmptyResponse); 23 | 24 | // Balance is used to determine balance. 25 | rpc Balance (BalanceRequest) returns (BalanceResponse); 26 | 27 | // EstimateFee estimates the fee of the payment. 28 | rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse); 29 | 30 | // SendPayment sends payment to the given recipient, 31 | // ensures in the validity of the receipt as well as the 32 | // account has enough money for doing that. 33 | rpc SendPayment (SendPaymentRequest) returns (Payment); 34 | 35 | // PaymentByID is used to fetch the information about payment, by the 36 | // given system payment id. 37 | rpc PaymentByID (PaymentByIDRequest) returns (Payment); 38 | 39 | // PaymentsByReceipt is used to fetch the information about payment, by the 40 | // given receipt. 41 | rpc PaymentsByReceipt (PaymentsByReceiptRequest) returns (PaymentsByReceiptResponse); 42 | 43 | // ListPayments returnes list of payment which were registered by the 44 | // system. 45 | rpc ListPayments (ListPaymentsRequest) returns (ListPaymentsResponse); 46 | ``` 47 | -------------------------------------------------------------------------------- /cmd/pscli/README.md: -------------------------------------------------------------------------------- 1 | Command line interface is an tool which helps to access payserver RPC API. 2 | -------------------------------------------------------------------------------- /cmd/pscli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "github.com/bitlum/connector/crpc" 7 | "github.com/urfave/cli" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | defaultRPCPort = "9002" 13 | defaultRPCHostPort = "localhost:" + defaultRPCPort 14 | ) 15 | 16 | func fatal(err error) { 17 | fmt.Fprintf(os.Stderr, "[pscli] %v\n", err) 18 | os.Exit(1) 19 | } 20 | 21 | func getClient(ctx *cli.Context) (crpc.PayServerClient, func()) { 22 | conn := getClientConn(ctx, false) 23 | 24 | cleanUp := func() { 25 | conn.Close() 26 | } 27 | 28 | return crpc.NewPayServerClient(conn), cleanUp 29 | } 30 | 31 | func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn { 32 | // Create a dial options array. 33 | opts := []grpc.DialOption{ 34 | grpc.WithInsecure(), 35 | } 36 | 37 | conn, err := grpc.Dial(ctx.GlobalString("rpcserver"), opts...) 38 | if err != nil { 39 | fatal(err) 40 | } 41 | 42 | return conn 43 | } 44 | 45 | func main() { 46 | app := cli.NewApp() 47 | app.Name = "pscli" 48 | app.Version = fmt.Sprintf("0.1") 49 | app.Usage = "Control plane for your PayServer Daemon (psd)" 50 | app.Flags = []cli.Flag{ 51 | cli.StringFlag{ 52 | Name: "rpcserver", 53 | Value: defaultRPCHostPort, 54 | Usage: "host:port of payserver", 55 | }, 56 | } 57 | app.Commands = []cli.Command{ 58 | createReceiptCommand, 59 | validateReceiptCommand, 60 | balanceCommand, 61 | estimateFeeCommand, 62 | sendPaymentCommand, 63 | paymentByIDCommand, 64 | paymentByReceiptCommand, 65 | listPaymentsCommand, 66 | } 67 | 68 | if err := app.Run(os.Args); err != nil { 69 | fatal(err) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/conversion.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | "math/big" 6 | "github.com/go-errors/errors" 7 | "github.com/btcsuite/btcutil" 8 | ) 9 | 10 | var SatoshiPerBitcoin = decimal.New(btcutil.SatoshiPerBitcoin, 0) 11 | 12 | func BtcStrToSatoshi(amount string) (int64, error) { 13 | amt, err := decimal.NewFromString(amount) 14 | if err != nil { 15 | return 0, errors.Errorf("unable to parse amount(%v): %v", 16 | amount, err) 17 | } 18 | 19 | a, _ := amt.Float64() 20 | btcAmount, err := btcutil.NewAmount(a) 21 | if err != nil { 22 | return 0, errors.Errorf("unable to parse amount(%v): %v", a, err) 23 | } 24 | 25 | return int64(btcAmount), nil 26 | } 27 | 28 | func Sat2DecAmount(amount btcutil.Amount) decimal.Decimal { 29 | amt := decimal.NewFromBigInt(big.NewInt(int64(amount)), 0) 30 | return amt.Div(SatoshiPerBitcoin) 31 | } 32 | -------------------------------------------------------------------------------- /common/log.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/btcsuite/btclog" 7 | ) 8 | 9 | // NamedLogger is an extension of btclog.Logger which adds additional name to 10 | // the logging. 11 | type NamedLogger struct { 12 | Logger btclog.Logger 13 | Name string 14 | } 15 | 16 | func (l *NamedLogger) convert(format string, params []interface{}) (string, 17 | []interface{}) { 18 | format = fmt.Sprintf("(%v) %v", "%v", format) 19 | var nparams []interface{} 20 | nparams = append(nparams, l.Name) 21 | nparams = append(nparams, params...) 22 | return format, nparams 23 | } 24 | 25 | // Tracef formats message according to format specifier and writes to 26 | // to log with LevelTrace. 27 | func (l *NamedLogger) Tracef(format string, params ...interface{}) { 28 | format, params = l.convert(format, params) 29 | l.Logger.Tracef(format, params...) 30 | } 31 | 32 | // Debugf formats message according to format specifier and writes to 33 | // log with LevelDebug. 34 | func (l *NamedLogger) Debugf(format string, params ...interface{}) { 35 | format, params = l.convert(format, params) 36 | l.Logger.Debugf(format, params...) 37 | } 38 | 39 | // Infof formats message according to format specifier and writes to 40 | // log with LevelInfo. 41 | func (l *NamedLogger) Infof(format string, params ...interface{}) { 42 | format, params = l.convert(format, params) 43 | l.Logger.Infof(format, params...) 44 | } 45 | 46 | // Warnf formats message according to format specifier and writes to 47 | // to log with LevelWarn. 48 | func (l *NamedLogger) Warnf(format string, params ...interface{}) { 49 | format, params = l.convert(format, params) 50 | l.Logger.Warnf(format, params...) 51 | } 52 | 53 | // Errorf formats message according to format specifier and writes to 54 | // to log with LevelError. 55 | func (l *NamedLogger) Errorf(format string, params ...interface{}) { 56 | format, params = l.convert(format, params) 57 | l.Logger.Errorf(format, params...) 58 | } 59 | 60 | // Criticalf formats message according to format specifier and writes to 61 | // log with LevelCritical. 62 | func (l *NamedLogger) Criticalf(format string, params ...interface{}) { 63 | format, params = l.convert(format, params) 64 | l.Logger.Criticalf(format, params...) 65 | } 66 | 67 | // Trace formats message using the default formats for its operands 68 | // and writes to log with LevelTrace. 69 | func (l *NamedLogger) Trace(v ...interface{}) { 70 | var k []interface{} 71 | k = append(k, fmt.Sprintf("(%v)", l.Name)) 72 | k = append(k, v...) 73 | l.Logger.Trace(k...) 74 | } 75 | 76 | // Debug formats message using the default formats for its operands 77 | // and writes to log with LevelDebug. 78 | func (l *NamedLogger) Debug(v ...interface{}) { 79 | var k []interface{} 80 | k = append(k, fmt.Sprintf("(%v)", l.Name)) 81 | k = append(k, v...) 82 | l.Logger.Debug(k...) 83 | } 84 | 85 | // Info formats message using the default formats for its operands 86 | // and writes to log with LevelInfo. 87 | func (l *NamedLogger) Info(v ...interface{}) { 88 | var k []interface{} 89 | k = append(k, fmt.Sprintf("(%v)", l.Name)) 90 | k = append(k, v...) 91 | l.Logger.Info(k...) 92 | } 93 | 94 | // Warn formats message using the default formats for its operands 95 | // and writes to log with LevelWarn. 96 | func (l *NamedLogger) Warn(v ...interface{}) { 97 | var k []interface{} 98 | k = append(k, fmt.Sprintf("(%v)", l.Name)) 99 | k = append(k, v...) 100 | l.Logger.Warn(k...) 101 | } 102 | 103 | // Error formats message using the default formats for its operands 104 | // and writes to log with LevelError. 105 | func (l *NamedLogger) Error(v ...interface{}) { 106 | var k []interface{} 107 | k = append(k, fmt.Sprintf("(%v)", l.Name)) 108 | k = append(k, v...) 109 | l.Logger.Error(k...) 110 | } 111 | 112 | // Critical formats message using the default formats for its operands 113 | // and writes to log with LevelCritical. 114 | func (l *NamedLogger) Critical(v ...interface{}) { 115 | var k []interface{} 116 | k = append(k, fmt.Sprintf("(%v)", l.Name)) 117 | k = append(k, v...) 118 | l.Logger.Critical(k...) 119 | } 120 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "strings" 5 | "runtime" 6 | ) 7 | 8 | // GetFunctionName() returns name of the function within which it executed. 9 | func GetFunctionName() string { 10 | pc, _, _, _ := runtime.Caller(1) 11 | fullName := runtime.FuncForPC(pc).Name() 12 | parts := strings.Split(fullName, ".") 13 | return parts[len(parts)-1] 14 | } 15 | -------------------------------------------------------------------------------- /connectors/daemons/bitcoind/tx_test.go: -------------------------------------------------------------------------------- 1 | package bitcoind 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors/rpc" 5 | "github.com/btcsuite/btcutil" 6 | "testing" 7 | ) 8 | 9 | // TestSimpleCreateReorganisationOutputs test creation of outputs without 10 | // specifying fee, to just check general logic. 11 | func TestSimpleCreateReorganisationOutputs(t *testing.T) { 12 | feeRatePerByte := uint64(0) 13 | 14 | outputValue := 1.0 15 | inputs := []rpc.UnspentInput{ 16 | { 17 | Amount: outputValue, 18 | }, 19 | { 20 | Amount: outputValue, 21 | }, 22 | } 23 | 24 | var overallUTXOAmount btcutil.Amount 25 | for _, input := range inputs { 26 | amount, _ := btcutil.NewAmount(input.Amount) 27 | overallUTXOAmount += amount 28 | } 29 | 30 | utxoValue, _ := btcutil.NewAmount(0.5) 31 | outputAmounts, fee, err := createReorganisationOutputs(feeRatePerByte, 32 | inputs, utxoValue) 33 | if err != nil { 34 | t.Fatalf("unable craft reoganisation outputs: %v", err) 35 | } 36 | 37 | overallOutputsAmount := btcutil.Amount(0) 38 | for _, amount := range outputAmounts { 39 | overallOutputsAmount += amount 40 | } 41 | 42 | if (overallUTXOAmount - overallOutputsAmount) != fee { 43 | t.Fatalf("returned fee is wrong") 44 | } 45 | 46 | if len(outputAmounts) != 4 { 47 | t.Fatalf("wrong outputs number") 48 | } 49 | } 50 | 51 | // TestCreateReorganisationOutputsWithFee take real case, and check that 52 | // function will behave properly. 53 | func TestCreateReorganisationOutputsWithFee(t *testing.T) { 54 | feeRatePerByte := uint64(10) 55 | 56 | outputValue := 0.5 57 | inputs := []rpc.UnspentInput{ 58 | { 59 | Amount: outputValue, 60 | }, 61 | { 62 | Amount: outputValue, 63 | }, 64 | } 65 | 66 | var overallUTXOAmount btcutil.Amount 67 | for _, input := range inputs { 68 | amount, _ := btcutil.NewAmount(input.Amount) 69 | overallUTXOAmount += amount 70 | } 71 | 72 | outputAmounts, fee, err := createReorganisationOutputs(feeRatePerByte, 73 | inputs, optimalUTXOValue) 74 | if err != nil { 75 | t.Fatalf("unable craft reoganisation outputs: %v", err) 76 | } 77 | 78 | overallOutputsAmount := btcutil.Amount(0) 79 | for _, amount := range outputAmounts { 80 | overallOutputsAmount += amount 81 | } 82 | 83 | if (overallUTXOAmount - overallOutputsAmount) != fee { 84 | t.Fatalf("returned fee is wrong") 85 | } 86 | 87 | if len(outputAmounts) != 124 { 88 | t.Fatalf("wrong number of outputs") 89 | } 90 | 91 | // Check all outputs except the last one which pays the fees, 92 | // than they are equal to the optimal value. 93 | for i := 0; i < len(outputAmounts)-1; i++ { 94 | if outputAmounts[i] != optimalUTXOValue { 95 | t.Fatalf("wrong output amount") 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /connectors/daemons/bitcoind/utils.go: -------------------------------------------------------------------------------- 1 | package bitcoind 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/bitlum/connector/connectors" 7 | "github.com/bitlum/connector/connectors/rpc/bitcoin" 8 | "github.com/bitlum/connector/connectors/rpc/bitcoincash" 9 | "github.com/bitlum/connector/connectors/rpc/dash" 10 | "github.com/bitlum/connector/connectors/rpc/litecoin" 11 | "github.com/btcsuite/btcd/chaincfg" 12 | "github.com/btcsuite/btcutil" 13 | "github.com/go-errors/errors" 14 | "github.com/shopspring/decimal" 15 | ) 16 | 17 | var satoshiPerBitcoin = decimal.New(btcutil.SatoshiPerBitcoin, 0) 18 | 19 | func decAmount2Sat(amount decimal.Decimal) btcutil.Amount { 20 | // If we would try to convert amount in float representation than it 21 | // could lead to precious error, for that reason convert in manually rather 22 | // than using btcutil.NewAmount(). 23 | return btcutil.Amount(amount.Mul(satoshiPerBitcoin).IntPart()) 24 | } 25 | 26 | func sat2DecAmount(amount btcutil.Amount) decimal.Decimal { 27 | amt := decimal.NewFromBigInt(big.NewInt(int64(amount)), 0) 28 | return amt.Div(satoshiPerBitcoin) 29 | } 30 | 31 | func printAmount(a btcutil.Amount) string { 32 | return decimal.NewFromFloat(a.ToBTC()).Round(8).String() 33 | } 34 | 35 | func isProperNet(desiredNet, actualNet string) bool { 36 | // Handle the case of different simulation networks names 37 | if desiredNet == "simnet" && actualNet == "regtest" { 38 | return true 39 | } 40 | 41 | // Handle the case of different testnet networks names 42 | if desiredNet == "testnet" && actualNet == "test" { 43 | return true 44 | } 45 | 46 | // Handle the case of different mainnet networks names 47 | if desiredNet == "mainnet" && actualNet == "main" { 48 | return true 49 | } 50 | 51 | return desiredNet == actualNet 52 | } 53 | 54 | func decodeAddress(asset connectors.Asset, address, 55 | network string) (btcutil.Address, error) { 56 | switch asset { 57 | case connectors.BTC: 58 | return bitcoin.DecodeAddress(address, network) 59 | case connectors.LTC: 60 | return litecoin.DecodeAddress(address, network) 61 | case connectors.BCH: 62 | return bitcoincash.DecodeAddress(address, network) 63 | case connectors.DASH: 64 | return dash.DecodeAddress(address, network) 65 | default: 66 | return nil, errors.Errorf("unsupported asset asset(%v)", asset) 67 | } 68 | } 69 | 70 | func getParams(asset connectors.Asset, network string) (*chaincfg.Params, error) { 71 | switch asset { 72 | case connectors.BTC: 73 | return bitcoin.GetParams(network) 74 | case connectors.LTC: 75 | return litecoin.GetParams(network) 76 | case connectors.BCH: 77 | return bitcoincash.GetParams(network) 78 | case connectors.DASH: 79 | return dash.GetParams(network) 80 | default: 81 | return nil, errors.Errorf("unsupported asset asset(%v)", asset) 82 | } 83 | } 84 | 85 | func accountToAlias(account string) connectors.AccountAlias { 86 | switch account { 87 | case defaultAccount: 88 | return connectors.DefaultAccount 89 | 90 | case allAccounts: 91 | return connectors.AllAccounts 92 | 93 | default: 94 | return connectors.AccountAlias(account) 95 | } 96 | } 97 | 98 | func aliasToAccount(alias connectors.AccountAlias) string { 99 | switch alias { 100 | case connectors.SentAccount: 101 | // In bitcoin-like asset you daemons is working in a way that you 102 | // could use use all the money from all accounts. 103 | return allAccounts 104 | 105 | case connectors.DefaultAccount: 106 | return defaultAccount 107 | 108 | case connectors.AllAccounts: 109 | return allAccounts 110 | 111 | default: 112 | return string(alias) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /connectors/daemons/bitcoind/utils_test.go: -------------------------------------------------------------------------------- 1 | package bitcoind 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | "testing" 6 | ) 7 | 8 | func TestDec2Amount(t *testing.T) { 9 | amt, err := decimal.NewFromString("0.01688044695939197") 10 | if err != nil { 11 | t.Fatalf("unable convert amount: %v", err) 12 | } 13 | 14 | if decAmount2Sat(amt) != 1688044 { 15 | t.Fatalf("wrong amount") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /connectors/daemons/bitcoind_simple/storage.go: -------------------------------------------------------------------------------- 1 | package bitcoind_simple 2 | 3 | // StateStorage is used to keep data which is needed for connector to 4 | // properly synchronise and track transactions. 5 | // 6 | // NOTE: This storage should be persistent. 7 | type StateStorage interface { 8 | // PutLastSyncedTxCounter is used to save last synchronised confirmed tx 9 | // counter. 10 | PutLastSyncedTxCounter(counter int) error 11 | 12 | // LastTxCounter is used to retrieve last synchronised confirmed tx 13 | // counter. 14 | LastTxCounter() (int, error) 15 | } 16 | -------------------------------------------------------------------------------- /connectors/daemons/bitcoind_simple/utils.go: -------------------------------------------------------------------------------- 1 | package bitcoind_simple 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/bitlum/connector/connectors" 7 | "github.com/bitlum/connector/connectors/rpc/bitcoin" 8 | "github.com/bitlum/connector/connectors/rpc/bitcoincash" 9 | "github.com/bitlum/connector/connectors/rpc/dash" 10 | "github.com/bitlum/connector/connectors/rpc/litecoin" 11 | "github.com/btcsuite/btcd/chaincfg" 12 | "github.com/btcsuite/btcutil" 13 | "github.com/go-errors/errors" 14 | "github.com/shopspring/decimal" 15 | ) 16 | 17 | var satoshiPerBitcoin = decimal.New(btcutil.SatoshiPerBitcoin, 0) 18 | 19 | func decAmount2Sat(amount decimal.Decimal) btcutil.Amount { 20 | // If we would try to convert amount in float representation than it 21 | // could lead to precious error, for that reason convert in manually rather 22 | // than using btcutil.NewAmount(). 23 | return btcutil.Amount(amount.Mul(satoshiPerBitcoin).IntPart()) 24 | } 25 | 26 | func sat2DecAmount(amount btcutil.Amount) decimal.Decimal { 27 | amt := decimal.NewFromBigInt(big.NewInt(int64(amount)), 0) 28 | return amt.Div(satoshiPerBitcoin).Round(8) 29 | } 30 | 31 | func printAmount(a btcutil.Amount) string { 32 | return decimal.NewFromFloat(a.ToBTC()).Round(8).String() 33 | } 34 | 35 | func isProperNet(desiredNet, actualNet string) bool { 36 | // Handle the case of different simulation networks names 37 | if desiredNet == "simnet" && actualNet == "regtest" { 38 | return true 39 | } 40 | 41 | // Handle the case of different testnet networks names 42 | if desiredNet == "testnet" && actualNet == "test" { 43 | return true 44 | } 45 | 46 | // Handle the case of different mainnet networks names 47 | if desiredNet == "mainnet" && actualNet == "main" { 48 | return true 49 | } 50 | 51 | return desiredNet == actualNet 52 | } 53 | 54 | func decodeAddress(asset connectors.Asset, address, 55 | network string) (btcutil.Address, error) { 56 | switch asset { 57 | case connectors.BTC: 58 | return bitcoin.DecodeAddress(address, network) 59 | case connectors.LTC: 60 | return litecoin.DecodeAddress(address, network) 61 | case connectors.BCH: 62 | return bitcoincash.DecodeAddress(address, network) 63 | case connectors.DASH: 64 | return dash.DecodeAddress(address, network) 65 | default: 66 | return nil, errors.Errorf("unsupported asset asset(%v)", asset) 67 | } 68 | } 69 | 70 | func getParams(asset connectors.Asset, network string) (*chaincfg.Params, error) { 71 | switch asset { 72 | case connectors.BTC: 73 | return bitcoin.GetParams(network) 74 | case connectors.LTC: 75 | return litecoin.GetParams(network) 76 | case connectors.BCH: 77 | return bitcoincash.GetParams(network) 78 | case connectors.DASH: 79 | return dash.GetParams(network) 80 | default: 81 | return nil, errors.Errorf("unsupported asset asset(%v)", asset) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /connectors/daemons/geth/client_test.go: -------------------------------------------------------------------------------- 1 | package geth 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "math/big" 8 | 9 | "github.com/onrik/ethrpc" 10 | ) 11 | 12 | func TestClient(t *testing.T) { 13 | client := ExtendedEthRpc{ethrpc.NewEthRPC("http://165.227.118.113:20306")} 14 | 15 | version, err := client.Web3ClientVersion() 16 | if err != nil { 17 | t.Fatalf("unable to get version: %v", err) 18 | } 19 | 20 | number, err := client.EthNewBlockFilter() 21 | if err != nil { 22 | t.Fatalf("unable to get version: %v", err) 23 | } 24 | 25 | pversion, err := client.EthProtocolVersion() 26 | if err != nil { 27 | t.Fatalf("unable to get version: %v", err) 28 | } 29 | 30 | nversion, err := client.NetVersion() 31 | if err != nil { 32 | t.Fatalf("unable to get version: %v", err) 33 | } 34 | 35 | account, err := client.PersonalNewAddress("kek") 36 | if err != nil { 37 | t.Fatalf("unable to get version: %v", err) 38 | } 39 | 40 | fmt.Println("version:", version) 41 | fmt.Println("number:", number) 42 | fmt.Println("pversion:", pversion) 43 | fmt.Println("nversion:", nversion) 44 | fmt.Println("account:", account) 45 | } 46 | 47 | func TestSignTransaction(t *testing.T) { 48 | client := ExtendedEthRpc{ethrpc.NewEthRPC("http://165.227.118.113:20306")} 49 | 50 | address, err := client.PersonalNewAddress("kek") 51 | if err != nil { 52 | t.Fatalf("unable to get version: %v", err) 53 | } 54 | 55 | _, err = client.PersonalUnlockAddress(address, "kek", 2) 56 | if err != nil { 57 | t.Fatalf("unable to unlock account: %v", err) 58 | } 59 | 60 | _, _, err = client.EthSignTransaction(ethrpc.T{ 61 | From: address, 62 | To: address, 63 | Gas: 21000, 64 | GasPrice: big.NewInt(21), 65 | Value: big.NewInt(1000), 66 | Data: "", 67 | Nonce: 0, 68 | }) 69 | if err != nil { 70 | t.Fatalf("unable to sign tx: %v", err) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /connectors/daemons/geth/errors.go: -------------------------------------------------------------------------------- 1 | package geth 2 | 3 | import "github.com/go-errors/errors" 4 | 5 | var ( 6 | ErrAccountAddressNotFound = errors.Errorf("unable to find account address") 7 | ) 8 | -------------------------------------------------------------------------------- /connectors/daemons/geth/extension.go: -------------------------------------------------------------------------------- 1 | package geth 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/onrik/ethrpc" 7 | ) 8 | 9 | type ExtendedEthRpc struct { 10 | *ethrpc.EthRPC 11 | } 12 | 13 | func (c *ExtendedEthRpc) PersonalNewAddress(pass string) (string, error) { 14 | var res string 15 | err := c.call("personal_newAccount", &res, pass) 16 | return res, err 17 | } 18 | 19 | func (c *ExtendedEthRpc) PersonalUnlockAddress(address, 20 | pass string, delay int) (bool, error) { 21 | var res bool 22 | err := c.call("personal_unlockAccount", &res, address, pass, delay) 23 | return res, err 24 | } 25 | 26 | func (c *ExtendedEthRpc) EthSignTransaction(t ethrpc.T) (*ethrpc.Transaction, 27 | string, error) { 28 | var resp struct { 29 | Tx *ethrpc.Transaction 30 | Raw string 31 | } 32 | err := c.call("eth_signTransaction", &resp, t) 33 | if err != nil { 34 | return nil, "", err 35 | } 36 | 37 | return resp.Tx, resp.Raw, err 38 | } 39 | 40 | // EthGetPendingTxs returns transactions from daemon mempool which are 41 | // belongs to one of our accounts. 42 | // 43 | // NOTE: Return both incoming pending transaction because we use 44 | // changed version of the geth client. 45 | func (c *ExtendedEthRpc) EthGetPendingTxs() ([]ethrpc.Transaction, error) { 46 | var txs []ethrpc.Transaction 47 | err := c.call("eth_pendingTransactions", &txs) 48 | return txs, err 49 | } 50 | 51 | // EthGasPrice returns the current price per gas in wei. 52 | func (c *ExtendedEthRpc) EthGasPrice() (string, error) { 53 | var response string 54 | if err := c.call("eth_gasPrice", &response); err != nil { 55 | return "", err 56 | } 57 | 58 | return response, nil 59 | } 60 | 61 | func (c *ExtendedEthRpc) call(method string, target interface{}, 62 | params ...interface{}) error { 63 | result, err := c.Call(method, params...) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | if target == nil { 69 | return nil 70 | } 71 | 72 | if err := json.Unmarshal(result, target); err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /connectors/daemons/geth/storage.go: -------------------------------------------------------------------------------- 1 | package geth 2 | 3 | // AccountsStorage is used to keep track connections between addresses and 4 | // accounts, because of the reason of Ethereum client not having this mapping 5 | // internally. 6 | // 7 | // NOTE: This storage has to be persistent. 8 | type AccountsStorage interface { 9 | // GetAccountByAddress returns account by given address. 10 | GetAccountByAddress(address string) (string, error) 11 | 12 | // GetAddressesByAccount returns addressed belonging to the given account. 13 | GetAddressesByAccount(account string) ([]string, error) 14 | 15 | // GetLastAccountAddress returns last address which were assigned to 16 | // account. 17 | GetLastAccountAddress(account string) (string, error) 18 | 19 | // AddAddressToAccount assigns new address to account. 20 | AddAddressToAccount(address, account string) error 21 | 22 | // AllAddresses returns all created addresses. 23 | AllAddresses() ([]string, error) 24 | 25 | // PutDefaultAddressNonce puts returns default address transaction nonce. 26 | // This method is needed because if we send transaction too frequently 27 | // ethereum transaction counter couldn't keep up and transaction fails, 28 | // because of replacement error. 29 | PutDefaultAddressNonce(nonce int) error 30 | 31 | // DefaultAddressNonce returns default address transaction nonce. 32 | // This method is needed because if we send transaction too frequently 33 | // ethereum transaction counter couldn't keep up and transaction fails, 34 | // because of replacement error. 35 | DefaultAddressNonce() (int, error) 36 | } 37 | -------------------------------------------------------------------------------- /connectors/daemons/geth/utils.go: -------------------------------------------------------------------------------- 1 | package geth 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors" 5 | "github.com/ethereum/go-ethereum/params" 6 | ) 7 | 8 | // pendingMap stores the information about pending transactions corresponding 9 | // to accounts. 10 | type pendingMap map[string]map[string]*connectors.Payment 11 | 12 | func (m pendingMap) add(tx *connectors.Payment) { 13 | if _, ok := m[tx.Account]; !ok { 14 | m[tx.Account] = make(map[string]*connectors.Payment) 15 | } 16 | 17 | m[tx.Account][tx.PaymentID] = tx 18 | } 19 | 20 | // merge merges two pending maps and invokes handler with new entry populated 21 | // as an argument. 22 | func (m pendingMap) merge(m2 pendingMap, 23 | newEntryHandler func(tx *connectors.Payment)) { 24 | for account, txs := range m2 { 25 | // Add all txs if there is no transaction for this 26 | // account and continue. 27 | if _, ok := m[account]; !ok { 28 | m[account] = txs 29 | 30 | for _, tx := range txs { 31 | newEntryHandler(tx) 32 | } 33 | 34 | continue 35 | } 36 | 37 | // If account exist that we should populate it 38 | // with transactions which aren't there yet. 39 | for txid, tx := range txs { 40 | if _, ok := m[account][txid]; !ok { 41 | m[account][txid] = tx 42 | newEntryHandler(tx) 43 | } 44 | } 45 | } 46 | 47 | for account, txs := range m { 48 | if _, ok := m2[account]; !ok { 49 | delete(m, account) 50 | continue 51 | } 52 | 53 | for txid, _ := range txs { 54 | if _, ok := m[account][txid]; !ok { 55 | delete(m[account], txid) 56 | } 57 | } 58 | } 59 | } 60 | 61 | func convertVersion(actualNet string) string { 62 | net := "simnet" 63 | 64 | switch actualNet { 65 | case params.RinkebyChainConfig.ChainID.String(): 66 | net = "testnet" 67 | case params.MainnetChainConfig.ChainID.String(): 68 | net = "mainnet" 69 | } 70 | 71 | return net 72 | } 73 | 74 | // generatePaymentID generates unique string based on the tx id and receive 75 | // address, which are together 76 | func generatePaymentID(txID, receiveAddress string, 77 | direction connectors.PaymentDirection, system connectors.PaymentSystem) string { 78 | return connectors.GeneratePaymentID(txID, receiveAddress, 79 | string(direction), string(system)) 80 | } 81 | -------------------------------------------------------------------------------- /connectors/daemons/lnd/log.go: -------------------------------------------------------------------------------- 1 | package lnd 2 | 3 | import ( 4 | "github.com/btcsuite/btclog" 5 | ) 6 | 7 | // log is a logger that is initialized with no output filters. This 8 | // means the package will not perform any logging by default until the caller 9 | // requests it. 10 | var log btclog.Logger 11 | 12 | // The default amount of logging is none. 13 | func init() { 14 | DisableLog() 15 | } 16 | 17 | // DisableLog disables all library log output. Logging output is disabled 18 | // by default until UseLogger is called. 19 | func DisableLog() { 20 | log = btclog.Disabled 21 | } 22 | 23 | // UseLogger uses a specified Logger to output package logging info. 24 | // This should be used in preference to SetLogWriter if the caller is also 25 | // using btclog. 26 | func UseLogger(logger btclog.Logger) { 27 | log = logger 28 | } 29 | 30 | // logClosure is used to provide a closure over expensive logging operations 31 | // so don't have to be performed when the logging level doesn't warrant it. 32 | type logClosure func() string 33 | 34 | // String invokes the underlying function and returns the result. 35 | func (c logClosure) String() string { 36 | return c() 37 | } 38 | 39 | // newLogClosure returns a new closure over a function that returns a string 40 | // which itself provides a Stringer interface so that it can be used with the 41 | // logging system. 42 | func newLogClosure(c func() string) logClosure { 43 | return logClosure(c) 44 | } 45 | -------------------------------------------------------------------------------- /connectors/daemons/lnd/utils.go: -------------------------------------------------------------------------------- 1 | package lnd 2 | 3 | import ( 4 | "github.com/btcsuite/btcutil" 5 | "github.com/pkg/errors" 6 | "github.com/shopspring/decimal" 7 | "math/big" 8 | "github.com/bitlum/connector/connectors" 9 | "io/ioutil" 10 | "strconv" 11 | "google.golang.org/grpc/credentials" 12 | "github.com/lightningnetwork/lnd/lnrpc" 13 | "google.golang.org/grpc" 14 | "gopkg.in/macaroon.v2" 15 | "github.com/lightningnetwork/lnd/macaroons" 16 | "net" 17 | ) 18 | 19 | var satoshiPerBitcoin = decimal.New(btcutil.SatoshiPerBitcoin, 0) 20 | 21 | func btcToSatoshi(amount string) (int64, error) { 22 | amt, err := decimal.NewFromString(amount) 23 | if err != nil { 24 | return 0, errors.Errorf("unable to parse amount(%v): %v", 25 | amount, err) 26 | } 27 | 28 | a, _ := amt.Float64() 29 | btcAmount, err := btcutil.NewAmount(a) 30 | if err != nil { 31 | return 0, errors.Errorf("unable to parse amount(%v): %v", a, err) 32 | } 33 | 34 | return int64(btcAmount), nil 35 | } 36 | 37 | func sat2DecAmount(amount btcutil.Amount) decimal.Decimal { 38 | amt := decimal.NewFromBigInt(big.NewInt(int64(amount)), 0) 39 | return amt.Div(satoshiPerBitcoin).Round(8) 40 | } 41 | 42 | func generatePaymentID(invoiceStr string, 43 | direction connectors.PaymentDirection) string { 44 | return connectors.GeneratePaymentID(invoiceStr, string(direction)) 45 | } 46 | 47 | // getClient return lightning network grpc client. 48 | func (c *Connector) getClient(macaroonPath string) (lnrpc.LightningClient, 49 | *grpc.ClientConn, error) { 50 | 51 | creds, err := credentials.NewClientTLSFromFile(c.cfg.TlsCertPath, "") 52 | if err != nil { 53 | return nil, nil, errors.Errorf("unable to load credentials: %v", err) 54 | } 55 | 56 | opts := []grpc.DialOption{ 57 | grpc.WithTransportCredentials(creds), 58 | } 59 | 60 | if macaroonPath != "" { 61 | macaroonBytes, err := ioutil.ReadFile(macaroonPath) 62 | if err != nil { 63 | return nil, nil, errors.Errorf("Unable to read macaroon file: %v", err) 64 | } 65 | 66 | mac := &macaroon.Macaroon{} 67 | if err = mac.UnmarshalBinary(macaroonBytes); err != nil { 68 | return nil, nil, errors.Errorf("Unable to unmarshal macaroon: %v", err) 69 | } 70 | 71 | opts = append(opts, 72 | grpc.WithPerRPCCredentials(macaroons.NewMacaroonCredential(mac))) 73 | } 74 | 75 | target := net.JoinHostPort(c.cfg.Host, strconv.Itoa(c.cfg.Port)) 76 | log.Infof("lightning client connection to lnd: %v", target) 77 | 78 | conn, err := grpc.Dial(target, opts...) 79 | if err != nil { 80 | return nil, nil, errors.Errorf("unable to to dial grpc: %v", err) 81 | } 82 | 83 | return lnrpc.NewLightningClient(conn), conn, nil 84 | } 85 | -------------------------------------------------------------------------------- /connectors/interface.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "github.com/lightningnetwork/lnd/lnrpc" 5 | "github.com/lightningnetwork/lnd/zpay32" 6 | "github.com/shopspring/decimal" 7 | ) 8 | 9 | type LightningInfo struct { 10 | Host string 11 | Port string 12 | MinAmount string 13 | MaxAmount string 14 | *lnrpc.GetInfoResponse 15 | } 16 | 17 | // BlockchainConnector is an interface which describes the blockchain service 18 | // which is able to connect to blockchain daemon of particular currency and 19 | // operate with transactions, addresses, and also able to notify other 20 | // subsystems when transaction passes required number of confirmations. 21 | type BlockchainConnector interface { 22 | // CreateAddress is used to create deposit address. 23 | CreateAddress() (string, error) 24 | 25 | // ConfirmedBalance return the amount of confirmed funds available for account. 26 | ConfirmedBalance() (decimal.Decimal, error) 27 | 28 | // PendingBalance return the amount of funds waiting to be confirmed. 29 | PendingBalance() (decimal.Decimal, error) 30 | 31 | // SendPayment sends payment with given amount to the given address. 32 | SendPayment(address, amount string) (*Payment, error) 33 | 34 | // ValidateAddress takes the blockchain address and ensure its valid. 35 | ValidateAddress(address string) error 36 | 37 | // EstimateFee estimate fee for the transaction with the given sending 38 | // amount. 39 | EstimateFee(amount string) (decimal.Decimal, error) 40 | } 41 | 42 | // LightningConnector is an interface which describes the service 43 | // which is able to connect lightning network daemon of particular currency and 44 | // operate with transactions, addresses, and also able to notify other 45 | // subsystems when invoice is settled. 46 | type LightningConnector interface { 47 | // Info returns the information about our lnd node. 48 | Info() (*LightningInfo, error) 49 | 50 | // CreateInvoice is used to create lightning network invoice. 51 | CreateInvoice(receipt, amount, description string) (string, 52 | *zpay32.Invoice, error) 53 | 54 | // SendTo is used to send specific amount of money to address within this 55 | // payment system. 56 | SendTo(invoice, amount string) (*Payment, error) 57 | 58 | // ConfirmedBalance return the amount of confirmed funds available for account. 59 | // TODO(andrew.shvv) Implement lightning wallet balance 60 | ConfirmedBalance() (decimal.Decimal, error) 61 | 62 | // PendingBalance return the amount of funds waiting to be confirmed. 63 | // TODO(andrew.shvv) Implement lightning wallet balance 64 | PendingBalance() (decimal.Decimal, error) 65 | 66 | // QueryRoutes returns list of routes from to the given lnd node, 67 | // and insures the the capacity of the channels is sufficient. 68 | QueryRoutes(pubKey, amount string, limit int32) ([]*lnrpc.Route, error) 69 | 70 | // ValidateInvoice takes the encoded lightning network invoice and ensure 71 | // its valid. 72 | ValidateInvoice(invoice, amount string) (*zpay32.Invoice, error) 73 | 74 | // EstimateFee estimate fee for the payment with the given sending 75 | // amount, to the given node. 76 | EstimateFee(invoice string) (decimal.Decimal, error) 77 | } 78 | -------------------------------------------------------------------------------- /connectors/payment_details.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "io" 5 | "encoding/json" 6 | "io/ioutil" 7 | ) 8 | 9 | // Serializable is an interface which defines a serializable 10 | // object. 11 | type Serializable interface { 12 | // Decode reads the bytes stream and converts it to the object. 13 | Decode(io.Reader, uint32) error 14 | 15 | // Encode converts object to the bytes stream and write it into the 16 | // writer. 17 | Encode(io.Writer, uint32) error 18 | } 19 | 20 | // Runtime check to ensure that BlockchainPendingDetails implements 21 | // Serializable interface. 22 | var _ Serializable = (*BlockchainPendingDetails)(nil) 23 | 24 | // Decode reads the bytes stream and converts it to the object. 25 | func (d *BlockchainPendingDetails) Decode(r io.Reader, v uint32) error { 26 | data, err := ioutil.ReadAll(r) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | return json.Unmarshal(data, d) 32 | } 33 | 34 | // Encode converts object to the bytes stream and write it into the 35 | // writer. 36 | func (d *BlockchainPendingDetails) Encode(w io.Writer, v uint32) error { 37 | data, err := json.Marshal(d) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | _, err = w.Write(data) 43 | return err 44 | } 45 | 46 | // GeneratedTxDetails is the string form of signed blockchain transaction 47 | // which. 48 | type GeneratedTxDetails struct { 49 | // RawTx byte representation of blockchain transaction. 50 | RawTx []byte 51 | 52 | // TxID blockchain identification of transaction. 53 | TxID string 54 | } 55 | 56 | // Runtime check to ensure that BlockchainPendingDetails implements 57 | // Serializable interface. 58 | var _ Serializable = (*GeneratedTxDetails)(nil) 59 | 60 | // Decode reads the bytes stream and converts it to the object. 61 | func (d *GeneratedTxDetails) Decode(r io.Reader, v uint32) error { 62 | data, err := ioutil.ReadAll(r) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | return json.Unmarshal(data, d) 68 | } 69 | 70 | // Encode converts object to the bytes stream and write it into the 71 | // writer. 72 | func (d *GeneratedTxDetails) Encode(w io.Writer, v uint32) error { 73 | data, err := json.Marshal(d) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | _, err = w.Write(data) 79 | return err 80 | } 81 | -------------------------------------------------------------------------------- /connectors/payment_details_test.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "testing" 5 | "bytes" 6 | "reflect" 7 | ) 8 | 9 | func TestBlockchainPendingDetailsEncodeDecode(t *testing.T) { 10 | d := &BlockchainPendingDetails{ 11 | Confirmations: 1, 12 | ConfirmationsLeft: 3, 13 | } 14 | 15 | var b bytes.Buffer 16 | if err := d.Encode(&b, 0); err != nil { 17 | t.Fatalf("unable to encode details: %v", err) 18 | } 19 | 20 | d1 := &BlockchainPendingDetails{} 21 | if err := d1.Decode(&b, 0); err != nil { 22 | t.Fatalf("unable to decode details: %v", err) 23 | } 24 | 25 | if !reflect.DeepEqual(d1, d) { 26 | t.Fatal("objects are different") 27 | } 28 | } 29 | 30 | func TestGeneratedTxDetailsEncodeDecode(t *testing.T) { 31 | d := &GeneratedTxDetails{ 32 | RawTx: []byte("rawtx"), 33 | TxID: "txid", 34 | } 35 | 36 | var b bytes.Buffer 37 | if err := d.Encode(&b, 0); err != nil { 38 | t.Fatalf("unable to encode details: %v", err) 39 | } 40 | 41 | d1 := &GeneratedTxDetails{} 42 | if err := d1.Decode(&b, 0); err != nil { 43 | t.Fatalf("unable to encode details: %v", err) 44 | } 45 | 46 | if !reflect.DeepEqual(d1, d) { 47 | t.Fatal("objects are different") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /connectors/rpc/bitcoin/params.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | "github.com/go-errors/errors" 6 | ) 7 | 8 | func GetParams(netName string) (*chaincfg.Params, error) { 9 | switch netName { 10 | case "mainnet", "main": 11 | return &chaincfg.MainNetParams, nil 12 | case "regtest", "simnet": 13 | return &chaincfg.RegressionNetParams, nil 14 | case "testnet3", "test", "testnet": 15 | return &chaincfg.TestNet3Params, nil 16 | } 17 | 18 | return nil, errors.Errorf("network '%s' is invalid or unsupported", 19 | netName) 20 | } 21 | -------------------------------------------------------------------------------- /connectors/rpc/bitcoin/validation.go: -------------------------------------------------------------------------------- 1 | package bitcoin 2 | 3 | import ( 4 | "github.com/btcsuite/btcutil" 5 | "github.com/go-errors/errors" 6 | ) 7 | 8 | func DecodeAddress(address, netName string) (btcutil.Address, error) { 9 | netParams, err := GetParams(netName) 10 | if err != nil { 11 | return nil, errors.Errorf("unable to get net params: %v", err) 12 | } 13 | 14 | decodedAddress, err := btcutil.DecodeAddress(address, netParams) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | if !decodedAddress.IsForNet(netParams) { 20 | return nil, errors.New("address is not for specified network") 21 | } 22 | 23 | return decodedAddress, nil 24 | } 25 | -------------------------------------------------------------------------------- /connectors/rpc/bitcoincash/client.go: -------------------------------------------------------------------------------- 1 | package bitcoincash 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors/rpc/bitcoin" 5 | "github.com/go-errors/errors" 6 | "github.com/bitlum/connector/connectors/rpc" 7 | "github.com/davecgh/go-spew/spew" 8 | "github.com/bitlum/connector/common" 9 | ) 10 | 11 | type ClientConfig bitcoin.ClientConfig 12 | type Client struct { 13 | *bitcoin.Client 14 | } 15 | 16 | // Runtime check to ensure that Client implements rpc.Client interface. 17 | var _ rpc.Client = (*Client)(nil) 18 | 19 | func NewClient(cfg ClientConfig) (*Client, error) { 20 | client, err := bitcoin.NewClient(bitcoin.ClientConfig(cfg)) 21 | return &Client{Client: client}, err 22 | } 23 | 24 | // NOTE: Part of the rpc.Client interface. 25 | func (c *Client) EstimateFee() (float64, error) { 26 | // Bitcoin Cash has removed estimatesmartfee in 17.2 version of their 27 | // client. 28 | res, err := c.Client.Daemon.EstimateFee(2) 29 | if err != nil { 30 | c.Logger.Tracef("method: %v, error: %v", err) 31 | return 0, err 32 | } 33 | 34 | if res == nil { 35 | err := errors.Errorf("result is nil") 36 | c.Logger.Tracef("method: %v, error: %v", err) 37 | return 0, err 38 | } 39 | 40 | feeRate := **res 41 | if feeRate <= 0 { 42 | err := errors.New("not enough data to make an estimation") 43 | c.Logger.Tracef("method: %v, error: %v", err) 44 | return 0, err 45 | } 46 | 47 | c.Logger.Tracef("method: %v, response: %v", common.GetFunctionName(), 48 | spew.Sdump(feeRate)) 49 | 50 | return feeRate, nil 51 | } 52 | -------------------------------------------------------------------------------- /connectors/rpc/bitcoincash/params.go: -------------------------------------------------------------------------------- 1 | package bitcoincash 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | "github.com/btcsuite/btcd/wire" 6 | "github.com/go-errors/errors" 7 | ) 8 | 9 | // chainIDPrefix is created to distinguish different chains during 10 | // the process of registration with btcutil mustRegister function. 11 | // 12 | // NOTE: This is needed because of the fact how btcutil DecodeAddress works, 13 | // it couldn't proper decode address if its networks wasn't previously 14 | // registered. 15 | var chainIDPrefix wire.BitcoinNet = 1 16 | 17 | var ( 18 | // Mainnet represents the main test network. 19 | Mainnet = wire.MainNet + chainIDPrefix 20 | 21 | // TestNet represents the regression test network. 22 | TestNet = wire.TestNet + chainIDPrefix 23 | 24 | // TestNet3 represents the test network. 25 | TestNet3 = wire.TestNet3 + chainIDPrefix 26 | ) 27 | 28 | // MainNetParams defines the network parameters for the main network. 29 | var MainNetParams = chaincfg.Params{ 30 | Net: Mainnet, 31 | Name: "mainnet", 32 | PubKeyHashAddrID: 0, // addresses start with 'X' 33 | ScriptHashAddrID: 5, // script addresses start with '7' 34 | PrivateKeyID: 128, // private keys start with '7' or 'X' 35 | 36 | // BIP32 hierarchical deterministic extended key magics 37 | HDPublicKeyID: [4]byte{0x04, 0x88, 0xb2, 0x1e}, // starts with xpub 38 | HDPrivateKeyID: [4]byte{0x04, 0x88, 0xad, 0xe4}, // starts with xprv 39 | } 40 | 41 | // TestNet3Params defines the network parameters for the test network 42 | // (version 3). Not to be confused with the regression test network, this 43 | // network is sometimes simply called "testnet". 44 | var TestNet3Params = chaincfg.Params{ 45 | Net: TestNet3, 46 | Name: "testnet3", 47 | PubKeyHashAddrID: 111, 48 | ScriptHashAddrID: 196, 49 | PrivateKeyID: 239, 50 | 51 | // BIP32 hierarchical deterministic extended key magics 52 | HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xCF}, 53 | HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, 54 | } 55 | 56 | // RegressionNetParams defines the network parameters for the regression test 57 | // Bitcoin Cash network. Not to be confused with the test network (version 58 | // 3), this network is sometimes simply called "testnet". 59 | var RegressionNetParams = chaincfg.Params{ 60 | Net: TestNet, 61 | Name: "regtest", 62 | PubKeyHashAddrID: 111, 63 | ScriptHashAddrID: 196, 64 | PrivateKeyID: 239, 65 | 66 | // BIP32 hierarchical deterministic extended key magics 67 | HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xCF}, 68 | HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, 69 | } 70 | 71 | // mustRegister performs the same function as Register except it panics if there 72 | // is an error. This should only be called from package init functions. 73 | func mustRegister(params *chaincfg.Params) { 74 | if err := chaincfg.Register(params); err != nil && 75 | err != chaincfg.ErrDuplicateNet { 76 | panic("failed to register network: " + err.Error()) 77 | } 78 | } 79 | 80 | func init() { 81 | mustRegister(&MainNetParams) 82 | mustRegister(&TestNet3Params) 83 | mustRegister(&RegressionNetParams) 84 | } 85 | 86 | func GetParams(netName string) (*chaincfg.Params, error) { 87 | switch netName { 88 | case "mainnet", "main": 89 | return &MainNetParams, nil 90 | case "regtest", "simnet": 91 | return &RegressionNetParams, nil 92 | case "testnet3", "test", "testnet": 93 | return &TestNet3Params, nil 94 | } 95 | 96 | return nil, errors.Errorf("network '%s' is invalid or unsupported", 97 | netName) 98 | } 99 | -------------------------------------------------------------------------------- /connectors/rpc/bitcoincash/validation.go: -------------------------------------------------------------------------------- 1 | package bitcoincash 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | "github.com/btcsuite/btcutil" 6 | "github.com/go-errors/errors" 7 | cashAddr "github.com/schancel/cashaddr-converter/address" 8 | ) 9 | 10 | // DecodeAddress validates bitcoin cash address according net, 11 | // and return btcutil compatible legacy bitcoin address. Supports cashaddr and 12 | // legacy addresses. 13 | // 14 | // NOTE: Regtest net treated as testnet 3.Ò 15 | func DecodeAddress(address, network string) (btcutil.Address, error) { 16 | netParams, err := GetParams(network) 17 | if err != nil { 18 | return nil, errors.Errorf("unable to get net params: %v", err) 19 | } 20 | 21 | // Check that address is valid, but if it return errors, 22 | // continue validation because it might be special "Cash Address" type. 23 | decodedAddress, err := btcutil.DecodeAddress(address, netParams) 24 | if err == nil { 25 | if decodedAddress.IsForNet(netParams) { 26 | return decodedAddress, nil 27 | } else { 28 | return nil, errors.New("address is not for specified network") 29 | } 30 | } 31 | 32 | cashAddress, err := cashAddr.NewFromString(address) 33 | if err != nil { 34 | return nil, errors.New("address neither legacy address nor cash addr") 35 | } 36 | 37 | legacyAddress, err := cashAddress.Legacy() 38 | if err != nil { 39 | return nil, errors.Errorf("unable convert to legacy address: %v", err) 40 | } 41 | 42 | address, err = legacyAddress.Encode() 43 | if err != nil { 44 | return nil, errors.Errorf("unable encode legacy address: %v", err) 45 | } 46 | 47 | decodedAddress, err = btcutil.DecodeAddress(address, netParams) 48 | if err == nil { 49 | if decodedAddress.IsForNet(netParams) { 50 | return decodedAddress, nil 51 | } else { 52 | return nil, errors.New("address is not for specified network") 53 | } 54 | } 55 | 56 | return decodedAddress, nil 57 | } 58 | 59 | func cashAddrNetToInt(networkType cashAddr.NetworkType) int { 60 | switch networkType { 61 | case cashAddr.MainNet: 62 | return 0 63 | case cashAddr.TestNet: 64 | return 1 65 | case cashAddr.RegTest: 66 | return 1 67 | } 68 | return 2 69 | } 70 | 71 | func bitcoinCashNetToInt(network *chaincfg.Params) int { 72 | switch network.Net { 73 | case Mainnet: 74 | return 0 75 | case TestNet3: 76 | return 1 77 | case TestNet: 78 | return 1 79 | } 80 | return 2 81 | } 82 | -------------------------------------------------------------------------------- /connectors/rpc/dash/client.go: -------------------------------------------------------------------------------- 1 | package dash 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/bitlum/connector/common" 6 | "github.com/bitlum/connector/connectors/rpc" 7 | "github.com/bitlum/connector/connectors/rpc/bitcoin" 8 | "github.com/bitlum/go-bitcoind-rpc/btcjson" 9 | "github.com/bitlum/go-bitcoind-rpc/rpcclient" 10 | "github.com/davecgh/go-spew/spew" 11 | "github.com/go-errors/errors" 12 | ) 13 | 14 | type ClientConfig bitcoin.ClientConfig 15 | type Client struct { 16 | *bitcoin.Client 17 | } 18 | 19 | // Runtime check to ensure that Client implements rpc.Client interface. 20 | var _ rpc.Client = (*Client)(nil) 21 | 22 | func NewClient(cfg ClientConfig) (*Client, error) { 23 | client, err := bitcoin.NewClient(bitcoin.ClientConfig(cfg)) 24 | return &Client{Client: client}, err 25 | } 26 | 27 | type getDashBlockChainInfoResult struct { 28 | *btcjson.GetBlockChainInfoResult 29 | 30 | // Override initial btcjson field with interface in order to avoid json 31 | // unmarshal error, because in dash the format of this field is different. 32 | Bip9SoftForks interface{} `json:"bip9_softforks"` 33 | } 34 | 35 | func (c *Client) GetBlockChainInfo() (*rpc.BlockChainInfoResp, error) { 36 | res := c.Daemon.GetBlockChainInfoAsync() 37 | info, err := receiveDashInfo(res) 38 | if err != nil { 39 | c.Logger.Tracef("method: %v, error: %v", err) 40 | return nil, err 41 | } 42 | 43 | resp := &rpc.BlockChainInfoResp{ 44 | Chain: info.Chain, 45 | } 46 | 47 | c.Logger.Tracef("method: %v, response: %v", common.GetFunctionName(), 48 | spew.Sdump(resp)) 49 | 50 | return resp, nil 51 | } 52 | 53 | func receiveDashInfo(r rpcclient.FutureGetBlockChainInfoResult) ( 54 | *getDashBlockChainInfoResult, error) { 55 | 56 | res, err := rpcclient.ReceiveFuture(r) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | var chainInfo getDashBlockChainInfoResult 62 | if err := json.Unmarshal(res, &chainInfo); err != nil { 63 | return nil, err 64 | } 65 | return &chainInfo, nil 66 | } 67 | 68 | func (c *Client) EstimateFee() (float64, error) { 69 | confTarget := uint32(2) 70 | res, err := c.Daemon.EstimateSmartFeeWithMode(confTarget, "") 71 | if err != nil { 72 | c.Logger.Tracef("method: %v, error: %v", common.GetFunctionName(), err) 73 | return 0, err 74 | } 75 | 76 | if res.Errors != nil { 77 | err := errors.New((*res.Errors)[0]) 78 | c.Logger.Tracef("method: %v, error: %v", common.GetFunctionName(), err) 79 | return 0, err 80 | } 81 | 82 | if res.FeeRate == nil { 83 | err := errors.Errorf("fee rate is nil") 84 | c.Logger.Tracef("method: %v, error: %v", common.GetFunctionName(), err) 85 | return 0, err 86 | } 87 | 88 | if res.Blocks != int(confTarget) { 89 | err := errors.New("not enough data to make an estimation") 90 | c.Logger.Tracef("method: %v, error: %v", common.GetFunctionName(), err) 91 | return 0, err 92 | } 93 | 94 | feeRate := *res.FeeRate 95 | if feeRate <= 0 { 96 | err := errors.New("not enough data to make an estimation") 97 | c.Logger.Tracef("method: %v, error: %v", common.GetFunctionName(), err) 98 | return 0, err 99 | } 100 | 101 | c.Logger.Tracef("method: %v, response: %v", common.GetFunctionName(), 102 | spew.Sdump(feeRate)) 103 | 104 | return feeRate, nil 105 | } -------------------------------------------------------------------------------- /connectors/rpc/dash/params.go: -------------------------------------------------------------------------------- 1 | package dash 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg" 5 | "github.com/btcsuite/btcd/wire" 6 | "github.com/go-errors/errors" 7 | ) 8 | 9 | // chainIDPrefix is created to distinguish different chains during 10 | // the process of registration with btcutil mustRegister function. 11 | // 12 | // NOTE: This is needed because of the fact how btcutil DecodeAddress works, 13 | // it couldn't proper decode address if its networks wasn't previously 14 | // registered. 15 | var chainIDPrefix wire.BitcoinNet = 2 16 | 17 | var ( 18 | // Mainnet represents the main network. 19 | Mainnet = wire.MainNet + chainIDPrefix 20 | 21 | // TestNet represents the regression network. 22 | TestNet = wire.TestNet + chainIDPrefix 23 | 24 | // TestNet3 represents the test network. 25 | TestNet3 = wire.TestNet3 + chainIDPrefix 26 | ) 27 | 28 | var MainNetParams = chaincfg.Params{ 29 | Net: Mainnet, 30 | Name: "mainnet", 31 | PubKeyHashAddrID: 76, // addresses start with 'X' 32 | ScriptHashAddrID: 16, // script addresses start with '7' 33 | PrivateKeyID: 204, // private keys start with '7' or 'X' 34 | 35 | // BIP32 hierarchical deterministic extended key magics 36 | HDPublicKeyID: [4]byte{0x04, 0x88, 0xb2, 0x1e}, // starts with xpub 37 | HDPrivateKeyID: [4]byte{0x04, 0x88, 0xad, 0xe4}, // starts with xprv 38 | } 39 | 40 | var TestNet3Params = chaincfg.Params{ 41 | Net: TestNet3, 42 | Name: "testnet3", 43 | PubKeyHashAddrID: 140, // addresses start with 'y' 44 | ScriptHashAddrID: 19, // script addresses start with '8' or '9' 45 | PrivateKeyID: 239, // private keys start with '9' or 'c' (Bitcoin defaults) 46 | 47 | // BIP32 hierarchical deterministic extended key magics 48 | HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xCF}, 49 | HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, 50 | } 51 | 52 | // RegressionNetParams defines the network parameters for the regression test 53 | // Dash network. Not to be confused with the test Dash network (version 54 | // 3), this network is sometimes simply called "testnet". 55 | var RegressionNetParams = chaincfg.Params{ 56 | Net: TestNet, 57 | Name: "regtest", 58 | PubKeyHashAddrID: 140, // addresses start with 'y' 59 | ScriptHashAddrID: 19, // script addresses start with '8' or '9' 60 | PrivateKeyID: 239, // private keys start with '9' or 'c' (Bitcoin defaults) 61 | 62 | // BIP32 hierarchical deterministic extended key magics 63 | HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xCF}, 64 | HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, 65 | } 66 | 67 | // mustRegister performs the same function as Register except it panics if there 68 | // is an error. This should only be called from package init functions. 69 | func mustRegister(params *chaincfg.Params) { 70 | if err := chaincfg.Register(params); err != nil && 71 | err != chaincfg.ErrDuplicateNet { 72 | panic("failed to register network: " + err.Error()) 73 | } 74 | } 75 | 76 | func init() { 77 | mustRegister(&MainNetParams) 78 | mustRegister(&TestNet3Params) 79 | mustRegister(&RegressionNetParams) 80 | } 81 | 82 | func GetParams(netName string) (*chaincfg.Params, error) { 83 | switch netName { 84 | case "mainnet", "main": 85 | return &MainNetParams, nil 86 | case "regtest", "simnet": 87 | return &RegressionNetParams, nil 88 | case "testnet3", "test", "testnet": 89 | return &TestNet3Params, nil 90 | } 91 | 92 | return nil, errors.Errorf("network '%s' is "+ 93 | "invalid or unsupported", netName) 94 | } 95 | -------------------------------------------------------------------------------- /connectors/rpc/dash/validation.go: -------------------------------------------------------------------------------- 1 | package dash 2 | 3 | import ( 4 | "github.com/go-errors/errors" 5 | "github.com/btcsuite/btcutil" 6 | ) 7 | 8 | // DecodeAddress ensures that address is valid and belongs to the given 9 | // network, returns decoded address. 10 | func DecodeAddress(address, netName string) (btcutil.Address, error) { 11 | netParams, err := GetParams(netName) 12 | if err != nil { 13 | return nil, errors.Errorf("unable to get net params: %v", err) 14 | } 15 | 16 | decodedAddress, err := btcutil.DecodeAddress(address, netParams) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | if !decodedAddress.IsForNet(netParams) { 22 | return nil, errors.New("address is not for specified network") 23 | } 24 | 25 | return decodedAddress, nil 26 | } 27 | -------------------------------------------------------------------------------- /connectors/rpc/ethereum/validation.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | eth "github.com/ethereum/go-ethereum/common" 8 | "github.com/go-errors/errors" 9 | ) 10 | 11 | var re = regexp.MustCompile(fmt.Sprintf(`(?:0x)?[0-9a-fA-F]{%d}`, 12 | eth.AddressLength*2)) 13 | 14 | // ValidateAddress validates Ethereum address. 15 | func ValidateAddress(address string) error { 16 | if !eth.IsHexAddress(address) || !re.MatchString(address) { 17 | return errors.Errorf("invalid hex address") 18 | } 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /connectors/rpc/ethereum/validation_test.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestValidateAddress(t *testing.T) { 8 | type args struct { 9 | asset string 10 | net string 11 | addr string 12 | } 13 | 14 | tests := []struct { 15 | name string 16 | args args 17 | wantErr bool 18 | }{ 19 | { 20 | name: "ETH empty net", 21 | args: args{"ETH", "", "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"}, 22 | wantErr: false, 23 | }, 24 | { 25 | name: "ETH mainnet", 26 | args: args{"ETH", "mainnet", "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"}, 27 | wantErr: false, 28 | }, 29 | { 30 | name: "ETH ropsten", 31 | args: args{"ETH", "ropsten", "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"}, 32 | wantErr: false, 33 | }, 34 | { 35 | name: "ETH kovan", 36 | args: args{"ETH", "kovan", "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"}, 37 | wantErr: false, 38 | }, 39 | { 40 | name: "ETH without prefix", 41 | args: args{"ETH", "", "de0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"}, 42 | wantErr: false, 43 | }, 44 | { 45 | name: "ETH invalid", 46 | args: args{"ETH", "", "0xdg0B295669a9FD93d5F28D9Ec85E40f4cb697BAe"}, 47 | wantErr: true, 48 | }, 49 | { 50 | name: "ETH BTC mainnet address", 51 | args: args{"ETH", "", "bc1qn6f5cd9rpxtgavsxyk7lgyvgn75mj8tc56aenn3yvck7d0x6sc0qgxs65c"}, 52 | wantErr: true, 53 | }, 54 | { 55 | name: "ETH LTC mainnet address", 56 | args: args{"ETH", "", "tltc1ql00u9jm8qwzhv4e53hthz34t5744wh4pdkyuny5fp3feklm8cjgscue3nw"}, 57 | wantErr: true, 58 | }, 59 | { 60 | name: "ETH DASH mainnet address", 61 | args: args{"ETH", "", "yYQRhUDfzXn4b6acrnenZdDGRTpDUTqmQs"}, 62 | wantErr: true, 63 | }, 64 | { 65 | name: "ETH random", 66 | args: args{"ETH", "", "dGj3h7mvUfYuLGX2LoemYxsMyBQo90qQ20"}, 67 | wantErr: true, 68 | }, 69 | { 70 | name: "ETH empty", 71 | args: args{"ETH", "", ""}, 72 | wantErr: true, 73 | }, 74 | } 75 | 76 | for _, tt := range tests { 77 | t.Run(tt.name, func(t *testing.T) { 78 | defer func() { 79 | if r := recover(); r != nil { 80 | t.Errorf("unexpected panic: %v", r) 81 | } 82 | }() 83 | var err error 84 | if err = ValidateAddress(tt.args.addr); (err != nil) != tt.wantErr { 85 | t.Errorf("error = %v, wantErr = %v", err, tt.wantErr) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /connectors/rpc/litecoin/client.go: -------------------------------------------------------------------------------- 1 | package litecoin 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors/rpc/bitcoin" 5 | "github.com/bitlum/connector/connectors/rpc" 6 | ) 7 | 8 | type ClientConfig bitcoin.ClientConfig 9 | 10 | // Client is identical to bitcoin implementation. 11 | type Client struct { 12 | *bitcoin.Client 13 | } 14 | 15 | // Runtime check to ensure that Client implements rpc.Client interface. 16 | var _ rpc.Client = (*Client)(nil) 17 | 18 | func NewClient(cfg ClientConfig) (*Client, error) { 19 | client, err := bitcoin.NewClient(bitcoin.ClientConfig(cfg)) 20 | return &Client{Client: client}, err 21 | } 22 | -------------------------------------------------------------------------------- /connectors/rpc/litecoin/validation.go: -------------------------------------------------------------------------------- 1 | package litecoin 2 | 3 | import ( 4 | "github.com/go-errors/errors" 5 | "github.com/btcsuite/btcd/chaincfg" 6 | "github.com/btcsuite/btcutil" 7 | ) 8 | 9 | // DecodeAddress ensures that address is valid and belongs to the given 10 | // network, and return decoded address. 11 | func DecodeAddress(address, netName string) (btcutil.Address, error) { 12 | netParams, err := GetParams(netName) 13 | if err != nil { 14 | return nil, errors.Errorf("unable to get net params: %v", err) 15 | } 16 | 17 | decodedAddress, err := decode(address, netParams) 18 | if err != nil { 19 | // If there is error we shouldn't return it in mainnet straight away, 20 | // but instead because there is a possibility of address belong to the 21 | // legacy litecoin address type we should make another check. 22 | if netName == "mainnet" { 23 | legacyNetParams, err := GetParams("mainnet-legacy") 24 | if err != nil { 25 | return nil, errors.Errorf("unable to get legacy mainnet params: %v", err) 26 | } 27 | 28 | return decode(address, legacyNetParams) 29 | } 30 | 31 | return nil, err 32 | } 33 | 34 | // If validation was successful, than address is valid and we should 35 | // exit. 36 | return decodedAddress, nil 37 | } 38 | 39 | func decode(address string, network *chaincfg.Params) (btcutil.Address, error) { 40 | decodedAddress, err := btcutil.DecodeAddress(address, network) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | if !decodedAddress.IsForNet(network) { 46 | return nil, errors.New("address is not for specified network") 47 | } 48 | 49 | return decodedAddress, nil 50 | } 51 | -------------------------------------------------------------------------------- /connectors/rpc/log.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/btcsuite/btclog" 5 | ) 6 | 7 | // log is a logger that is initialized with no output filters. This 8 | // means the package will not perform any logging by default until the caller 9 | // requests it. 10 | var log btclog.Logger 11 | 12 | // The default amount of logging is none. 13 | func init() { 14 | DisableLog() 15 | } 16 | 17 | // DisableLog disables all library log output. Logging output is disabled 18 | // by default until UseLogger is called. 19 | func DisableLog() { 20 | log = btclog.Disabled 21 | } 22 | 23 | // UseLogger uses a specified Logger to output package logging info. 24 | // This should be used in preference to SetLogWriter if the caller is also 25 | // using btclog. 26 | func UseLogger(logger btclog.Logger) { 27 | log = logger 28 | } 29 | 30 | // logClosure is used to provide a closure over expensive logging operations 31 | // so don't have to be performed when the logging level doesn't warrant it. 32 | type logClosure func() string 33 | 34 | // String invokes the underlying function and returns the result. 35 | func (c logClosure) String() string { 36 | return c() 37 | } 38 | 39 | // newLogClosure returns a new closure over a function that returns a string 40 | // which itself provides a Stringer interface so that it can be used with the 41 | // logging system. 42 | func newLogClosure(c func() string) logClosure { 43 | return logClosure(c) 44 | } 45 | -------------------------------------------------------------------------------- /connectors/storage.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "github.com/go-errors/errors" 5 | ) 6 | 7 | // PaymentStorage is an external storage for payments, it is used by 8 | // connector to save payment as well as update its state. 9 | type PaymentsStore interface { 10 | // PaymentByID returns payment by id. 11 | PaymentByID(paymentID string) (*Payment, error) 12 | 13 | // PaymentByReceipt returns payment by receipt. 14 | PaymentByReceipt(receipt string) ([]*Payment, error) 15 | 16 | // SavePayment add payment to the store. 17 | SavePayment(payment *Payment) error 18 | 19 | // ListPayments return list of all payments. 20 | ListPayments(asset Asset, status PaymentStatus, direction PaymentDirection, 21 | media PaymentMedia, system PaymentSystem) ([]*Payment, error) 22 | } 23 | 24 | var PaymentNotFound = errors.New("payment not found") 25 | 26 | // StateStorage is used to keep data which is needed for connector to 27 | // properly synchronise and track transactions. 28 | // 29 | // NOTE: This storage should be persistent. 30 | type StateStorage interface { 31 | // PutLastSyncedHash is used to save last synchronised block hash. 32 | PutLastSyncedHash(hash []byte) error 33 | 34 | // LastSyncedHash is used to retrieve last synchronised block hash. 35 | LastSyncedHash() ([]byte, error) 36 | } 37 | -------------------------------------------------------------------------------- /crpc/README.md: -------------------------------------------------------------------------------- 1 | The `rpc` package is using `gRPC` - open source remote procedure call (RPC) 2 | system initially developed at Google. It uses HTTP/2 for transport, Protocol 3 | Buffers as the interface description language, and provides features such as 4 | authentication, bidirectional streaming and flow control, blocking or 5 | non-blocking bindings, and cancellation and timeouts. 6 | 7 | With `rpc.proto` API schema defined we generate cross-platform clients for many 8 | languages, if API had changed, the generated clients also will be changed, and 9 | what you have to do is to update API by swapping old files with new one. 10 | 11 | If you want to connect to exchange server via `gRPC` from web browser, for 12 | example if you are developing browser extension which connects to our 13 | exchange, than you should use, `grpc-web-client` and one of the generated 14 | clients: 15 | * `js-web` - client generated for making requests to exchange server from 16 | browser using javascript. [Usage example]() 17 | * `ts-web` - client generated for making requests to exchange server from 18 | browser using typescript. [Usage example]() 19 | 20 | If you want to connect to exchange server via `gRPC` from your own server, 21 | for example if you are developing trading bot, you should use: 22 | * `go` - client generated for usage in golang. [Usage example]() 23 | * `js-node` - client generated for usage in nodejs. [Usage example]() 24 | -------------------------------------------------------------------------------- /crpc/errors.go: -------------------------------------------------------------------------------- 1 | package crpc 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | ErrAssetNotSupported = iota + 1 9 | ErrNetworkNotSupported 10 | ErrInvalidArgument 11 | 12 | // ErrInternal... 13 | ErrInternal 14 | ) 15 | 16 | type Error struct { 17 | code int 18 | errMsg string 19 | internal bool 20 | } 21 | 22 | func (e Error) Error() string { 23 | return e.errMsg 24 | } 25 | 26 | func newErrNetworkNotSupported(network, operation string) Error { 27 | return Error{ 28 | code: ErrNetworkNotSupported, 29 | errMsg: fmt.Sprintf("%v: operation \"%v\" isn't supported for network %v", 30 | ErrNetworkNotSupported, operation, network), 31 | } 32 | } 33 | 34 | func newErrAssetNotSupported(asset, media string) Error { 35 | return Error{ 36 | code: ErrAssetNotSupported, 37 | errMsg: fmt.Sprintf("%v: asset(%v) is not supported for media(%v)", 38 | ErrAssetNotSupported, asset, media), 39 | } 40 | } 41 | 42 | func newErrInternal(desc string) Error { 43 | return Error{ 44 | code: ErrInternal, 45 | errMsg: fmt.Sprintf("%v: internal error: %v", ErrInternal, desc), 46 | } 47 | } 48 | 49 | func newErrInvalidArgument(argName string) Error { 50 | return Error{ 51 | code: ErrInvalidArgument, 52 | errMsg: fmt.Sprintf("%v: invalid argument '%v'", ErrInvalidArgument, 53 | argName), 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crpc/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Generate the protos. 4 | protoc -I/usr/local/include -I. \ 5 | -I$GOPATH/src \ 6 | --go_out=plugins=grpc:. \ 7 | rpc.proto 8 | -------------------------------------------------------------------------------- /crpc/log.go: -------------------------------------------------------------------------------- 1 | package crpc 2 | 3 | import ( 4 | "github.com/btcsuite/btclog" 5 | ) 6 | 7 | // log is a logger that is initialized with no output filters. This 8 | // means the package will not perform any logging by default until the caller 9 | // requests it. 10 | var log btclog.Logger 11 | 12 | // The default amount of logging is none. 13 | func init() { 14 | DisableLog() 15 | } 16 | 17 | // DisableLog disables all library log output. Logging output is disabled 18 | // by default until UseLogger is called. 19 | func DisableLog() { 20 | log = btclog.Disabled 21 | } 22 | 23 | // UseLogger uses a specified Logger to output package logging info. 24 | // This should be used in preference to SetLogWriter if the caller is also 25 | // using btclog. 26 | func UseLogger(logger btclog.Logger) { 27 | log = logger 28 | } 29 | 30 | // logClosure is used to provide a closure over expensive logging operations 31 | // so don't have to be performed when the logging level doesn't warrant it. 32 | type logClosure func() string 33 | 34 | // String invokes the underlying function and returns the result. 35 | func (c logClosure) String() string { 36 | return c() 37 | } 38 | 39 | // newLogClosure returns a new closure over a function that returns a string 40 | // which itself provides a Stringer interface so that it can be used with the 41 | // logging system. 42 | func newLogClosure(c func() string) logClosure { 43 | return logClosure(c) 44 | } 45 | -------------------------------------------------------------------------------- /db/inmemory/payments.go: -------------------------------------------------------------------------------- 1 | package inmemory 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors" 5 | "sort" 6 | "sync" 7 | ) 8 | 9 | // MemoryPaymentsStore is a `PaymentStore` in-memory implementation, 10 | // which is able to periodically clean stored payments which are exceed 11 | // storing time. 12 | type MemoryPaymentsStore struct { 13 | paymentsMutex sync.RWMutex 14 | paymentsByID map[string]*connectors.Payment 15 | } 16 | 17 | // Runtime check to ensure that MemoryPaymentsStore implements 18 | // connectors.PaymentsStore interface. 19 | var _ connectors.PaymentsStore = (*MemoryPaymentsStore)(nil) 20 | 21 | // NewMemoryPaymentsStore creates new memory payment store with specified 22 | // payments storing time. 23 | func NewMemoryPaymentsStore() *MemoryPaymentsStore { 24 | return &MemoryPaymentsStore{ 25 | paymentsByID: make(map[string]*connectors.Payment), 26 | } 27 | } 28 | 29 | // PaymentByID return payment by given id. 30 | func (s *MemoryPaymentsStore) PaymentByID(id string) (*connectors.Payment, error) { 31 | s.paymentsMutex.RLock() 32 | defer s.paymentsMutex.RUnlock() 33 | 34 | p, exists := s.paymentsByID[id] 35 | if !exists { 36 | return nil, connectors.PaymentNotFound 37 | } 38 | return p, nil 39 | } 40 | 41 | // PaymentByReceipt return payment by given receipt. 42 | func (s *MemoryPaymentsStore) PaymentByReceipt(receipt string) ([]*connectors.Payment, error) { 43 | s.paymentsMutex.RLock() 44 | defer s.paymentsMutex.RUnlock() 45 | 46 | var payments []*connectors.Payment 47 | for _, payment := range s.paymentsByID { 48 | if payment.Receipt == receipt { 49 | payments = append(payments, payment) 50 | } 51 | } 52 | 53 | sort.Slice(payments, func(i, j int) bool { 54 | return payments[i].UpdatedAt > payments[j].UpdatedAt 55 | }) 56 | 57 | return payments, nil 58 | } 59 | 60 | // SavePayment adds payment to the store. 61 | func (s *MemoryPaymentsStore) SavePayment(p *connectors.Payment) error { 62 | s.paymentsMutex.Lock() 63 | defer s.paymentsMutex.Unlock() 64 | 65 | payment := &connectors.Payment{} 66 | *payment = *p 67 | s.paymentsByID[p.PaymentID] = payment 68 | return nil 69 | } 70 | 71 | // ListPayments return list of all payments. 72 | func (s *MemoryPaymentsStore) ListPayments(asset connectors.Asset, 73 | status connectors.PaymentStatus, direction connectors.PaymentDirection, 74 | media connectors.PaymentMedia, system connectors.PaymentSystem) ([]*connectors.Payment, error) { 75 | 76 | s.paymentsMutex.RLock() 77 | defer s.paymentsMutex.RUnlock() 78 | 79 | var payments []*connectors.Payment 80 | for _, payment := range s.paymentsByID { 81 | if asset != "" && payment.Asset != asset { 82 | continue 83 | } 84 | 85 | if status != "" && payment.Status != status { 86 | continue 87 | } 88 | 89 | if direction != "" && payment.Direction != direction { 90 | continue 91 | } 92 | 93 | if media != "" && payment.Media != media { 94 | continue 95 | } 96 | 97 | if system != "" && payment.System != system { 98 | continue 99 | } 100 | 101 | payments = append(payments, payment) 102 | } 103 | 104 | sort.Slice(payments, func(i, j int) bool { 105 | return payments[i].UpdatedAt > payments[j].UpdatedAt 106 | }) 107 | 108 | return payments, nil 109 | } 110 | -------------------------------------------------------------------------------- /db/sqlite/bitcoin_simnet_state.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors" 5 | "github.com/bitlum/connector/connectors/daemons/bitcoind_simple" 6 | "github.com/jinzhu/gorm" 7 | "time" 8 | ) 9 | 10 | type BitcoinSimpleState struct { 11 | CreatedAt time.Time 12 | UpdatedAt time.Time 13 | 14 | Asset string `gorm:"primary_key"` 15 | TxCounter int 16 | } 17 | 18 | type BitcoinSimpleStateStorage struct { 19 | db *DB 20 | asset connectors.Asset 21 | } 22 | 23 | func NewBitcoinSimpleStateStorage(asset connectors.Asset, 24 | db *DB) *BitcoinSimpleStateStorage { 25 | return &BitcoinSimpleStateStorage{ 26 | asset: asset, 27 | db: db, 28 | } 29 | } 30 | 31 | // Runtime check to ensure that ConnectorStateStorage implements 32 | // bitcoind_simple.StateStorage interface. 33 | var _ bitcoind_simple.StateStorage = (*BitcoinSimpleStateStorage)(nil) 34 | 35 | // PutLastSyncedTxCounter is used to save last synchronised confirmed tx 36 | // counter. 37 | // 38 | // NOTE: Part of the bitcoind_simple.StateStorage interface. 39 | func (s *BitcoinSimpleStateStorage) PutLastSyncedTxCounter(counter int) error { 40 | s.db.globalMutex.Lock() 41 | defer s.db.globalMutex.Unlock() 42 | 43 | return s.db.Save(&BitcoinSimpleState{ 44 | Asset: string(s.asset), 45 | TxCounter: counter, 46 | }).Error 47 | } 48 | 49 | // LastTxCounter is used to retrieve last synchronised confirmed tx 50 | // counter. 51 | // 52 | // NOTE: Part of the bitcoind_simple.StateStorage interface. 53 | func (s *BitcoinSimpleStateStorage) LastTxCounter() (int, error) { 54 | s.db.globalMutex.Lock() 55 | defer s.db.globalMutex.Unlock() 56 | 57 | state := &BitcoinSimpleState{} 58 | err := s.db.Where("asset = ?", string(s.asset)).Find(state).Error 59 | if gorm.IsRecordNotFoundError(err) { 60 | return 0, nil 61 | } else if err != nil { 62 | return 0, err 63 | } 64 | 65 | return state.TxCounter, nil 66 | } 67 | -------------------------------------------------------------------------------- /db/sqlite/connector_state.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors" 5 | "time" 6 | ) 7 | 8 | type ConnectorState struct { 9 | CreatedAt time.Time 10 | UpdatedAt time.Time 11 | 12 | Asset string `gorm:"primary_key"` 13 | LastHash string 14 | } 15 | 16 | type ConnectorStateStorage struct { 17 | db *DB 18 | asset connectors.Asset 19 | } 20 | 21 | func NewConnectorStateStorage(asset connectors.Asset, 22 | db *DB) *ConnectorStateStorage { 23 | return &ConnectorStateStorage{ 24 | asset: asset, 25 | db: db, 26 | } 27 | } 28 | 29 | // Runtime check to ensure that ConnectorStateStorage implements 30 | // connector.StateStorage interface. 31 | var _ connectors.StateStorage = (*ConnectorStateStorage)(nil) 32 | 33 | // PutLastSyncedHash is used to save last synchronised block hash. 34 | // 35 | // NOTE: Part of the bitcoind.Storage interface. 36 | func (s *ConnectorStateStorage) PutLastSyncedHash(hash []byte) error { 37 | s.db.globalMutex.Lock() 38 | defer s.db.globalMutex.Unlock() 39 | 40 | return s.db.Save(&ConnectorState{ 41 | Asset: string(s.asset), 42 | LastHash: string(hash), 43 | }).Error 44 | } 45 | 46 | // LastSyncedHash is used to retrieve last synchronised block hash. 47 | // 48 | // NOTE: Part of the bitcoind.Storage interface. 49 | func (s *ConnectorStateStorage) LastSyncedHash() ([]byte, error) { 50 | s.db.globalMutex.Lock() 51 | defer s.db.globalMutex.Unlock() 52 | 53 | state := &ConnectorState{} 54 | if err := s.db.Where("asset = ?", string(s.asset)). 55 | Find(state).Error; err != nil { 56 | return nil, err 57 | } 58 | 59 | return []byte(state.LastHash), nil 60 | } 61 | -------------------------------------------------------------------------------- /db/sqlite/connector_state_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | "bytes" 6 | "github.com/bitlum/connector/connectors" 7 | ) 8 | 9 | func TestPutLastHash(t *testing.T) { 10 | db, clear, err := MakeTestDB() 11 | if err != nil { 12 | t.Fatalf("unable to create test database: %v", err) 13 | } 14 | defer clear() 15 | 16 | btcStateStorage := NewConnectorStateStorage(connectors.BTC, db) 17 | err = btcStateStorage.PutLastSyncedHash([]byte("btc_hash")) 18 | if err != nil { 19 | t.Fatalf("unable to put hash: %v", err) 20 | } 21 | 22 | err = btcStateStorage.PutLastSyncedHash([]byte("btc_hash_next")) 23 | if err != nil { 24 | t.Fatalf("unable to put hash: %v", err) 25 | } 26 | 27 | btcHash, err := btcStateStorage.LastSyncedHash() 28 | if err != nil { 29 | t.Fatalf("unable to get hash: %v", err) 30 | } 31 | 32 | if !bytes.Equal(btcHash, []byte("btc_hash_next")) { 33 | t.Fatalf("wrong hash") 34 | } 35 | 36 | ethStateStorage := NewConnectorStateStorage(connectors.ETH, db) 37 | err = ethStateStorage.PutLastSyncedHash([]byte("eth_hash")) 38 | if err != nil { 39 | t.Fatalf("unable to put hash: %v", err) 40 | } 41 | 42 | err = ethStateStorage.PutLastSyncedHash([]byte("eth_hash_next")) 43 | if err != nil { 44 | t.Fatalf("unable to put hash: %v", err) 45 | } 46 | 47 | ethHash, err := ethStateStorage.LastSyncedHash() 48 | if err != nil { 49 | t.Fatalf("unable to get hash: %v", err) 50 | } 51 | 52 | if !bytes.Equal(ethHash, []byte("eth_hash_next")) { 53 | t.Fatalf("wrong hash") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /db/sqlite/db.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/bitlum/graphql-go/errors" 5 | "github.com/jinzhu/gorm" 6 | _ "github.com/jinzhu/gorm/dialects/sqlite" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | ) 11 | 12 | // db is the primary datastore. 13 | type DB struct { 14 | *gorm.DB 15 | dbPath string 16 | 17 | // globalMutex is used in order to avoid "database is locked" issue, 18 | // when two threads try to access database instead of waiting it just 19 | // return an error. 20 | globalMutex sync.Mutex 21 | } 22 | 23 | // Open opens an existing db. Any necessary schemas migrations due to 24 | // updates will take place as necessary. 25 | func Open(dbPath string, dbName string, shouldMigrate bool) (*DB, error) { 26 | path := filepath.Join(dbPath, dbName) 27 | 28 | if !fileExists(dbPath) { 29 | if err := os.MkdirAll(dbPath, 0700); err != nil { 30 | return nil, err 31 | } 32 | } 33 | 34 | gdb, err := gorm.Open("sqlite3", path) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | db := &DB{ 40 | DB: gdb, 41 | dbPath: dbPath, 42 | } 43 | 44 | if shouldMigrate { 45 | if err := db.Migrate(); err != nil { 46 | return nil, errors.Errorf("unable to migrate: %v", err) 47 | } 48 | } 49 | 50 | return db, nil 51 | } 52 | 53 | // fileExists returns true if the file exists, and false otherwise. 54 | func fileExists(path string) bool { 55 | if _, err := os.Stat(path); err != nil { 56 | if os.IsNotExist(err) { 57 | return false 58 | } 59 | } 60 | 61 | return true 62 | } 63 | -------------------------------------------------------------------------------- /db/sqlite/geth_accounts_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | "github.com/davecgh/go-spew/spew" 6 | ) 7 | 8 | func TestAccountsStorage(t *testing.T) { 9 | db, clear, err := MakeTestDB() 10 | if err != nil { 11 | t.Fatalf("unable to create test database: %v", err) 12 | } 13 | defer clear() 14 | 15 | storage := NewGethAccountsStorage(db) 16 | 17 | err = storage.AddAddressToAccount("address1", "account1") 18 | if err != nil { 19 | t.Fatalf("unable to add address: %v", err) 20 | } 21 | 22 | err = storage.AddAddressToAccount("address2", "account2") 23 | if err != nil { 24 | t.Fatalf("unable to add address: %v", err) 25 | } 26 | 27 | account, err := storage.GetAccountByAddress("address1") 28 | if err != nil { 29 | t.Fatalf("unable to get account: %v", err) 30 | } 31 | 32 | if account != "account1" { 33 | t.Fatalf("wrong account") 34 | } 35 | 36 | address, err := storage.GetLastAccountAddress("account1") 37 | if err != nil { 38 | t.Fatalf("unable to get address: %v", err) 39 | } 40 | 41 | if address != "address1" { 42 | t.Fatalf("wrong address") 43 | } 44 | 45 | addresses, err := storage.GetAddressesByAccount("account2") 46 | if err != nil { 47 | t.Fatalf("unable to get addresses: %v", err) 48 | } 49 | 50 | if len(addresses) != 1 { 51 | t.Fatalf("wrong len") 52 | } 53 | 54 | if addresses[0] != "address2" { 55 | t.Fatalf("wrong address") 56 | } 57 | 58 | allAddresses, err := storage.AllAddresses() 59 | if err != nil { 60 | t.Fatalf("unable to get all addreses: %v", err) 61 | } 62 | 63 | if len(allAddresses) != 2 { 64 | t.Fatalf("wrong len") 65 | } 66 | 67 | emptyAddress, err := storage.GetAccountByAddress("") 68 | if err != nil { 69 | t.Fatalf("unable to get address: %v", err) 70 | } 71 | 72 | spew.Dump(emptyAddress) 73 | if emptyAddress != "" { 74 | t.Fatalf("wrong address") 75 | } 76 | } 77 | 78 | func TestEthereumState(t *testing.T) { 79 | db, clear, err := MakeTestDB() 80 | if err != nil { 81 | t.Fatalf("unable to create test database: %v", err) 82 | } 83 | defer clear() 84 | 85 | s := NewGethAccountsStorage(db) 86 | 87 | if err := s.PutDefaultAddressNonce(10); err != nil { 88 | t.Fatalf("unable to put default address nonce: %v", err) 89 | } 90 | 91 | if nonce, err := s.DefaultAddressNonce(); err != nil { 92 | t.Fatalf("unable to put default address nonce: %v", err) 93 | } else { 94 | if nonce != 10 { 95 | t.Fatalf("wrong nonce") 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /db/sqlite/log.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/btcsuite/btclog" 5 | ) 6 | 7 | // log is a logger that is initialized with no output filters. This 8 | // means the package will not perform any logging by default until the caller 9 | // requests it. 10 | var log btclog.Logger 11 | 12 | // The default amount of logging is none. 13 | func init() { 14 | DisableLog() 15 | } 16 | 17 | // DisableLog disables all library log output. Logging output is disabled 18 | // by default until UseLogger is called. 19 | func DisableLog() { 20 | log = btclog.Disabled 21 | } 22 | 23 | // UseLogger uses a specified Logger to output package logging info. 24 | // This should be used in preference to SetLogWriter if the caller is also 25 | // using btclog. 26 | func UseLogger(logger btclog.Logger) { 27 | log = logger 28 | } 29 | 30 | // logClosure is used to provide a closure over expensive logging operations 31 | // so don't have to be performed when the logging level doesn't warrant it. 32 | type logClosure func() string 33 | 34 | // String invokes the underlying function and returns the result. 35 | func (c logClosure) String() string { 36 | return c() 37 | } 38 | 39 | // newLogClosure returns a new closure over a function that returns a string 40 | // which itself provides a Stringer interface so that it can be used with the 41 | // logging system. 42 | func newLogClosure(c func() string) logClosure { 43 | return logClosure(c) 44 | } 45 | -------------------------------------------------------------------------------- /db/sqlite/migrations.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors" 5 | "github.com/jinzhu/gorm" 6 | "gopkg.in/gormigrate.v1" 7 | ) 8 | 9 | func (db *DB) Migrate() error { 10 | if err := db.DB.AutoMigrate( 11 | &EthereumState{}, 12 | &ConnectorState{}, 13 | &EthereumAddress{}, 14 | &Payment{}, 15 | &BitcoinSimpleState{}, 16 | ).Error; err != nil { 17 | return err 18 | } 19 | 20 | return migrate(db.DB, allMigrations) 21 | } 22 | 23 | func migrate(gdb *gorm.DB, migrations []*gormigrate.Migration) error { 24 | return gormigrate.New(gdb, gormigrate.DefaultOptions, migrations).Migrate() 25 | } 26 | 27 | var allMigrations = []*gormigrate.Migration{ 28 | addPaymentSystemType, 29 | } 30 | 31 | var addPaymentSystemType = &gormigrate.Migration{ 32 | ID: "add_payment_system_type", 33 | Migrate: func(tx *gorm.DB) error { 34 | store := PaymentsStore{db: &DB{DB: tx}} 35 | 36 | // Previous internal payment direction now should be 37 | // moved in system field. All previous internal 38 | // payment where incoming. 39 | payments, err := store.ListPayments("", "", 40 | "", "", "") 41 | if err != nil { 42 | return err 43 | } 44 | 45 | for _, payment := range payments { 46 | // Internal transaction were tracked unproperly previously. 47 | if payment.Direction == "Internal" { 48 | err := tx.Delete(&Payment{}, "payment_id = ?", 49 | payment.PaymentID).Error 50 | if err != nil { 51 | return err 52 | } 53 | 54 | continue 55 | } 56 | 57 | // All other transactions were external 58 | oldID := payment.PaymentID 59 | err := tx.Delete(&Payment{}, "payment_id = ?", oldID).Error 60 | if err != nil { 61 | return err 62 | } 63 | 64 | payment.System = connectors.External 65 | payment.PaymentID, err = payment.GenPaymentID() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | log.Infof("Payment migration (%v) => (%v)", oldID, payment.PaymentID) 71 | if err := store.SavePayment(payment); err != nil { 72 | return err 73 | } 74 | } 75 | 76 | return nil 77 | }, 78 | } 79 | -------------------------------------------------------------------------------- /db/sqlite/migrations_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "gopkg.in/gormigrate.v1" 5 | "testing" 6 | ) 7 | 8 | func TestAddPaymentStatusMigration(t *testing.T) { 9 | db, err := Open("./", "test_db_add_status_field", false) 10 | if err != nil { 11 | t.Fatalf("unable create test db: %v", err) 12 | } 13 | 14 | tx := db.Begin() 15 | defer tx.Rollback() 16 | 17 | err = migrate(tx, []*gormigrate.Migration{addPaymentSystemType}) 18 | if err != nil { 19 | t.Fatalf("unable migrate db: %v", err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /db/sqlite/payment_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/bitlum/connector/connectors" 5 | "github.com/shopspring/decimal" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestPaymentsStorage(t *testing.T) { 11 | db, clear, err := MakeTestDB() 12 | if err != nil { 13 | t.Fatalf("unable to create test database: %v", err) 14 | } 15 | defer clear() 16 | 17 | store := PaymentsStore{db: db} 18 | 19 | paymentsBefore := []*connectors.Payment{ 20 | { 21 | 22 | PaymentID: "1", 23 | UpdatedAt: 1, 24 | Status: connectors.Pending, 25 | System: connectors.Internal, 26 | Direction: connectors.Outgoing, 27 | Receipt: "receipt", 28 | Asset: connectors.BTC, 29 | Account: "account", 30 | Media: connectors.Blockchain, 31 | Amount: decimal.NewFromFloat(1.1), 32 | MediaFee: decimal.NewFromFloat(1.1), 33 | MediaID: "media_id", 34 | Detail: &connectors.BlockchainPendingDetails{ 35 | ConfirmationsLeft: 3, 36 | Confirmations: 1, 37 | }, 38 | }, 39 | { 40 | 41 | PaymentID: "2", 42 | UpdatedAt: 2, 43 | Status: connectors.Completed, 44 | Direction: connectors.Incoming, 45 | System: connectors.External, 46 | Receipt: "receipt", 47 | Asset: connectors.ETH, 48 | Account: "account", 49 | Media: connectors.Lightning, 50 | Amount: decimal.NewFromFloat(1.1), 51 | MediaFee: decimal.NewFromFloat(1.1), 52 | MediaID: "media_id", 53 | Detail: &connectors.GeneratedTxDetails{ 54 | RawTx: []byte("rawtx"), 55 | TxID: "123", 56 | }, 57 | }, 58 | } 59 | 60 | if err := store.SavePayment(paymentsBefore[0]); err != nil { 61 | t.Fatalf("unable to save payment: %v", err) 62 | } 63 | 64 | if err := store.SavePayment(paymentsBefore[1]); err != nil { 65 | t.Fatalf("unable to save payment: %v", err) 66 | } 67 | 68 | paymentsAfter, err := store.ListPayments("", "", "", "", "") 69 | if err != nil { 70 | t.Fatalf("unable to get payments: %v", err) 71 | } 72 | 73 | if !reflect.DeepEqual(paymentsBefore, paymentsAfter) { 74 | t.Fatalf("wrong data") 75 | } 76 | 77 | { 78 | payment, err := store.PaymentByID("1") 79 | if err != nil { 80 | t.Fatalf("unable to get payment by receipt: %v", err) 81 | } 82 | 83 | if !reflect.DeepEqual(payment, paymentsAfter[0]) { 84 | t.Fatalf("wrong data") 85 | } 86 | } 87 | 88 | { 89 | payments, err := store.PaymentByReceipt("receipt") 90 | if err != nil { 91 | t.Fatalf("unable to get payment by receipt: %v", err) 92 | } 93 | 94 | if !reflect.DeepEqual(payments, paymentsAfter) { 95 | t.Fatalf("wrong data") 96 | } 97 | } 98 | 99 | { 100 | payments, err := store.ListPayments(connectors.ETH, "", "", "", "") 101 | if err != nil { 102 | t.Fatalf("unable to list payments: %v", err) 103 | } 104 | 105 | if !reflect.DeepEqual(payments[0], paymentsAfter[1]) { 106 | t.Fatalf("wrong data") 107 | } 108 | } 109 | 110 | { 111 | payments, err := store.ListPayments("", connectors.Completed, "", "", "") 112 | if err != nil { 113 | t.Fatalf("unable to list payments: %v", err) 114 | } 115 | 116 | if !reflect.DeepEqual(payments[0], paymentsAfter[1]) { 117 | t.Fatalf("wrong data") 118 | } 119 | } 120 | 121 | { 122 | payments, err := store.ListPayments("", "", connectors.Outgoing, "", "") 123 | if err != nil { 124 | t.Fatalf("unable to list payments: %v", err) 125 | } 126 | 127 | if !reflect.DeepEqual(payments[0], paymentsAfter[0]) { 128 | t.Fatalf("wrong data") 129 | } 130 | } 131 | 132 | { 133 | payments, err := store.ListPayments("", "", "", connectors.Lightning, "") 134 | if err != nil { 135 | t.Fatalf("unable to list payments: %v", err) 136 | } 137 | 138 | if !reflect.DeepEqual(payments[0], paymentsAfter[1]) { 139 | t.Fatalf("wrong data") 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /db/sqlite/test_db_add_status_field: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlum/payserver/d531a97c63b00c812b49d8a816c620b17f0b6ddb/db/sqlite/test_db_add_status_field -------------------------------------------------------------------------------- /db/sqlite/utils.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | ) 7 | 8 | // MakeTestDB creates a new instance of the ChannelDB for testing purposes. A 9 | // callback which cleans up the created temporary directories is also returned 10 | // and intended to be executed after the test completes. 11 | func MakeTestDB() (*DB, func(), error) { 12 | // First, create a temporary directory to be used for the duration of 13 | // this test. 14 | tempDirName, err := ioutil.TempDir("", "db") 15 | if err != nil { 16 | return nil, nil, err 17 | } 18 | 19 | db, err := Open(tempDirName, "sqlite.db", true) 20 | if err != nil { 21 | return nil, nil, err 22 | } 23 | 24 | cleanUp := func() { 25 | db.Close() 26 | os.RemoveAll(tempDirName) 27 | } 28 | 29 | return db, cleanUp, nil 30 | } 31 | -------------------------------------------------------------------------------- /docker/mainnet/.env.empty: -------------------------------------------------------------------------------- 1 | # This file contains list of environment variables required to deploy 2 | # mainnet connector dockers. You should copy this file to `.env` and 3 | # fill the latter with right values 4 | 5 | # *_AUTH variables should contain authorization string generated by 6 | # `rpcauth.py` script from. You can get it from https://github.com/bitcoin/bitcoin/tree/master/share/rpcauth 7 | # *_USER and *_PASSWORD variables should corresponds to *_AUTH string 8 | 9 | # *_VERSION variables should contain version in N.M.O format. This 10 | # variables are used to construct download link from official sources 11 | # of compiled binaries. So you should be sure that version you specifing 12 | # can be download. Check corresponding `Dockerfile` to get exact download 13 | # link. 14 | 15 | # *_REVISION variables specifies revision or branch which will be used. 16 | # As *_VERSION variables they are used to construct download link of 17 | # source code archive. 18 | # We encourage you to use revision a.k.a commit hash and do not use 19 | # branch name because docker-compose has cache and after first deploy 20 | # downloaded branch is freezed and further deploys will not download 21 | # new branch revisions. In contrast commit specifies exact 22 | # revision of source code so cache is working as should. 23 | 24 | EXTERNAL_IP= 25 | PRIVATE_IP= 26 | 27 | # `connector` docker container uses `github.com/bitlum/connector`. This 28 | # variable controls which revision (or branch) will be used. 29 | CONNECTOR_REVISION= 30 | 31 | # Force hash is used when connector need to skip syncing of blocks, 32 | # or roll back. 33 | CONNECTOR_BITCOIN_FORCE_HASH= 34 | CONNECTOR_LITECOIN_FORCE_HASH= 35 | CONNECTOR_DASH_FORCE_HASH= 36 | CONNECTOR_BITCOIN_CASH_FORCE_HASH= 37 | CONNECTOR_ETHEREUM_FORCE_HASH= 38 | 39 | BITCOIN_VERSION= 40 | BITCOIN_RPC_AUTH= 41 | BITCOIN_RPC_USER= 42 | BITCOIN_RPC_PASSWORD= 43 | 44 | # `bitcoin-lightning` docker container uses `lnd` from `github.com/lightningnetwork/lnd` 45 | # repo. This variable controls which revision (or branch) will be used. 46 | BITCOIN_LIGHTNING_REVISION= 47 | 48 | # `bitcoin-neutrino` docker container uses btcd from `github.com/btcsuite/btcd` 49 | # repo. This variable controls which revision (or branch) will be used. 50 | BITCOIN_NEUTRION_REVISION= 51 | BITCOIN_NEUTRINO_RPC_USER= 52 | BITCOIN_NEUTRINO_RPC_PASSWORD= 53 | 54 | BITCOIN_CASH_VERSION= 55 | BITCOIN_CASH_RPC_AUTH= 56 | BITCOIN_CASH_RPC_USER= 57 | BITCOIN_CASH_RPC_PASSWORD= 58 | 59 | DASH_VERSION= 60 | DASH_RPC_AUTH= 61 | DASH_RPC_USER= 62 | DASH_RPC_PASSWORD= 63 | 64 | # `ethereum` docker container uses `geth` from `github.com/bitlum/go-ethereum` 65 | # repo. This variable controls which revision (or branch) will be used. 66 | ETHEREUM_REVISION= 67 | 68 | LITECOIN_VERSION= 69 | LITECOIN_RPC_AUTH= 70 | LITECOIN_RPC_USER= 71 | LITECOIN_RPC_PASSWORD= -------------------------------------------------------------------------------- /docker/mainnet/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-cash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_CASH_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://download.bitcoinabc.org/$BITCOIN_CASH_VERSION/linux/bitcoin-abc-${BITCOIN_CASH_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/bitcoind \ 13 | */bin/bitcoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 9332 21 | 22 | # P2P port. 23 | EXPOSE 9333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder bitcoind /usr/local/bin/bitcoin-cashd 27 | COPY --from=builder bitcoin-cli /usr/local/bin/bitcoin-cash-cli 28 | 29 | # Default config used to initalize datadir volume at first or 30 | # cleaned deploy. It will be restored and used after each restart. 31 | COPY bitcoin-cash.mainnet.conf /root/default/bitcoin.conf 32 | 33 | # Entrypoint script used to init datadir if required and for 34 | # starting bitcoin cash daemon. 35 | COPY entrypoint.sh /root/ 36 | 37 | # We are using exec syntax to enable gracefull shutdown. Check 38 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 39 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-cash/bitcoin-cash.mainnet.conf: -------------------------------------------------------------------------------- 1 | port=9333 2 | 3 | rpcbind=0.0.0.0 4 | rpcport=9332 5 | 6 | # Allow RPC for connector docker. 7 | rpcallowip=172.100.2.100 8 | 9 | txindex=1 10 | usecashaddr=0 11 | 12 | printtoconsole=1 13 | 14 | discover=1 15 | listen=1 16 | server=1 -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-cash/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.bitcoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/bitcoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/bitcoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec bitcoin-cashd $EXTERNAL_IP_OPT \ 33 | --rpcauth=$BITCOIN_CASH_RPC_AUTH -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-lightning/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11-alpine as builder 2 | 3 | ARG BITCOIN_LIGHTNING_REVISION 4 | 5 | # Install dependencies and install/build lnd. 6 | RUN apk add --no-cache --update alpine-sdk \ 7 | git \ 8 | make 9 | 10 | WORKDIR $GOPATH/src/github.com/lightningnetwork/lnd 11 | 12 | # Copy from repository to build from. 13 | RUN git clone https://github.com/lightningnetwork/lnd.git /go/src/github.com/lightningnetwork/lnd 14 | 15 | # Force Go to use the cgo based DNS resolver. This is required to ensure DNS 16 | # queries required to connect to linked containers succeed. 17 | ENV GODEBUG netdns=cgo 18 | 19 | RUN cd /go/src/github.com/lightningnetwork/lnd \ 20 | && git checkout $BITCOIN_LIGHTNING_REVISION \ 21 | && make build-itest \ 22 | && mv lnd-itest /go/bin/lnd \ 23 | && mv lncli-itest /go/bin/lncli 24 | 25 | # Start a new, final image to reduce size. 26 | FROM alpine as final 27 | 28 | # Expose lnd ports (server, rpc). 29 | EXPOSE 9735 10009 30 | 31 | # Copy the binaries and entrypoint from the builder image. 32 | COPY --from=builder /go/bin/lncli /bin/ 33 | COPY --from=builder /go/bin/lnd /bin/ 34 | 35 | # Default config used to initalize datadir volume at first or 36 | # cleaned deploy. It will be restored and used after each restart. 37 | COPY bitcoin-lightning.mainnet.conf /root/default/lnd.conf 38 | 39 | # Add bash. 40 | RUN apk add --no-cache \ 41 | bash 42 | 43 | # Entrypoint script used to init datadir if required and for 44 | # starting lightning daemon. 45 | COPY entrypoint.sh /root/ 46 | 47 | # We are using exec syntax to enable gracefull shutdown. Check 48 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 49 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-lightning/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make lnd data persistent. 4 | DATA_DIR=/root/.lnd 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/lnd.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [ ! -d $DATA_DIR ]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp $DEFAULTS_DIR/lnd.conf $CONFIG 22 | 23 | # If external IP defined we need to set corresponding run option 24 | if [ ! -z "$EXTERNAL_IP" ]; then 25 | echo "Setting external IP" 26 | EXTERNAL_IP_OPT="--externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | RPC_USER_OPT="--bitcoind.rpcuser=" 30 | 31 | # We are using `exec` to enable gracefull shutdown of running daemon. 32 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 33 | exec lnd $EXTERNAL_IP_OPT \ 34 | --bitcoind.rpcuser=$BITCOIN_RPC_USER \ 35 | --bitcoind.rpcpass=$BITCOIN_RPC_PASSWORD -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-neutrino/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG BITCOIN_NEUTRINO_REVISION 4 | 5 | RUN go get -u github.com/Masterminds/glide 6 | 7 | WORKDIR $GOPATH/src/github.com/btcsuite/btcd 8 | 9 | RUN curl -L https://github.com/btcsuite/btcd/archive/$BITCOIN_NEUTRINO_REVISION.tar.gz \ 10 | | tar xz --strip 1 11 | 12 | RUN glide install 13 | 14 | RUN go install -v . ./cmd/btcctl 15 | 16 | 17 | 18 | FROM ubuntu:18.04 19 | 20 | # P2P port. 21 | EXPOSE 18333 22 | 23 | # Copying required binaries from builder stage. 24 | COPY --from=builder /go/bin/btcd /go/bin/btcctl /usr/local/bin/ 25 | 26 | # Default config used to initalize datadir volume at first or 27 | # cleaned deploy. It will be restored and used after each restart. 28 | COPY bitcoin-neutrino.mainnet.conf /root/default/btcd.conf 29 | 30 | # Entrypoint script used to init datadir if required and for 31 | # starting bitcoin daemon. 32 | COPY entrypoint.sh /root/ 33 | 34 | # We are using exec syntax to enable gracefull shutdown. Check 35 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 36 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/bitcoin-neutrino/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make lnd data persistent. 4 | DATA_DIR=/root/.btcd 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/btcd.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [ ! -d $DATA_DIR ]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp $DEFAULTS_DIR/btcd.conf $CONFIG 22 | 23 | # If external IP defined we need to set corresponding run option 24 | if [ ! -z "$EXTERNAL_IP" ]; then 25 | echo "Setting external IP" 26 | EXTERNAL_IP_OPT="--externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | RPC_USER_OPT="--bitcoind.rpcuser=" 30 | 31 | # We are using `exec` to enable gracefull shutdown of running daemon. 32 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 33 | exec btcd $EXTERNAL_IP_OPT \ 34 | --rpcuser=$BITCOIN_NEUTRINO_RPC_USER 35 | --rpcpass=$BITCOIN_NEUTRINO_RPC_PASSWORD -------------------------------------------------------------------------------- /docker/mainnet/bitcoin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://bitcoin.org/bin/bitcoin-core-$BITCOIN_VERSION/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/bitcoind \ 13 | */bin/bitcoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 13332 21 | 22 | # P2P port. 23 | EXPOSE 13333 24 | 25 | # `zmqpubrawblock` and `zmqpubrawtx` port. 26 | EXPOSE 8334 27 | 28 | # Copying required binaries from builder stage. 29 | COPY --from=builder bitcoind bitcoin-cli /usr/local/bin/ 30 | 31 | # Default config used to initalize datadir volume at first or 32 | # cleaned deploy. It will be restored and used after each restart. 33 | COPY bitcoin.mainnet.conf /root/default/bitcoin.conf 34 | 35 | # Entrypoint script used to init datadir if required and for 36 | # starting bitcoin daemon. 37 | COPY entrypoint.sh /root/ 38 | 39 | # We are using exec syntax to enable gracefull shutdown. Check 40 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 41 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/bitcoin/bitcoin.mainnet.conf: -------------------------------------------------------------------------------- 1 | port=8333 2 | 3 | rpcbind=0.0.0.0 4 | rpcport=8332 5 | 6 | # Allow RPC for connector docker. 7 | rpcallowip=172.100.2.100 8 | 9 | # Allow RPC for bitcoin-lightning docker. 10 | rpcallowip=172.100.2.101 11 | 12 | # Allow RPC for hub docker. 13 | rpcallowip=172.100.2.103 14 | 15 | # Increasing rcpthreads to workaround possible bug described here 16 | # https://github.com/lightningnetwork/lnd/issues/1174. 17 | rpcthreads=16 18 | 19 | # Tx index is required by lnd. 20 | txindex=1 21 | 22 | printtoconsole=1 23 | 24 | zmqpubrawblock=tcp://0.0.0.0:8334 25 | zmqpubrawtx=tcp://0.0.0.0:8335 26 | 27 | discover=1 28 | listen=1 29 | server=1 30 | 31 | # getaddressesbyaccount is deprecated and will be removed in V0.18. 32 | # but we need it right now. 33 | deprecatedrpc=accounts 34 | deprecatedrpc=signrawtransaction -------------------------------------------------------------------------------- /docker/mainnet/bitcoin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.bitcoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/bitcoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/bitcoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec bitcoind $EXTERNAL_IP_OPT \ 33 | --rpcauth=$BITCOIN_RPC_AUTH -------------------------------------------------------------------------------- /docker/mainnet/connector/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12 AS builder 2 | 3 | WORKDIR $GOPATH/src/github.com/bitlum/connector/ 4 | 5 | ARG CONNECTOR_REVISION 6 | 7 | RUN curl -L https://github.com/bitlum/connector/archive/$CONNECTOR_REVISION.tar.gz \ 8 | | tar xz --strip 1 9 | 10 | RUN GO111MODULE=on go get 11 | RUN GO111MODULE=on go install . ./cmd/... 12 | 13 | FROM ubuntu:18.04 14 | 15 | RUN apt-get update && apt-get install -y \ 16 | ca-certificates \ 17 | curl \ 18 | && rm -rf /var/lib/apt/lists/* 19 | 20 | # Copying required binaries from builder stage. 21 | COPY --from=builder /go/bin/connector /usr/local/bin 22 | COPY --from=builder /go/bin/pscli /usr/local/bin 23 | 24 | # Default config used to initalize datadir volume at first or 25 | # cleaned deploy. It will be restored and used after each restart. 26 | COPY connector.mainnet.conf /root/default/connector.conf 27 | 28 | # Entrypoint script used to init datadir if required and for 29 | # starting daemon 30 | COPY entrypoint.sh /root/ 31 | 32 | # We are using exec syntax to enable gracefull shutdown. Check 33 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 34 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] 35 | -------------------------------------------------------------------------------- /docker/mainnet/connector/connector.mainnet.conf: -------------------------------------------------------------------------------- 1 | [Application Options] 2 | datadir=/root/.connector 3 | debuglevel=trace 4 | network=mainnet 5 | 6 | # RPC address to bind 7 | rpchost=0.0.0.0 8 | rpcport=9002 9 | 10 | [Bitcoin] 11 | bitcoin.disable=false 12 | bitcoin.minconfirmations=1 13 | bitcoin.syncdelay=5 14 | bitcoin.host=bitcoin.mainnet 15 | bitcoin.port=8332 16 | 17 | # From https://bitcoinfees.earn.com/ at 2018-07-02 fastest fee for byte 18 | # is 130. In connector we implemented unit as weight, so this parameter 19 | # is fee per weight. 20 | bitcoin.feeperunit=130 21 | 22 | [Bitcoincash] 23 | bitcoincash.disable=false 24 | bitcoincash.minconfirmations=1 25 | bitcoincash.syncdelay=5 26 | bitcoincash.host=bitcoin-cash.mainnet 27 | bitcoincash.port=9332 28 | 29 | # Didn't find any certain info. We will adjust this during tests and 30 | # further development. For now lets take big enough using 31 | # https://jochen-hoenicke.de/queue/#3,24h to be more than mosts. 32 | # At 2018-07-02 this was 20. 33 | bitcoincash.feeperunit=20 34 | 35 | [Dash] 36 | dash.disable=false 37 | dash.minconfirmations=1 38 | dash.syncdelay=5 39 | dash.host=dash.mainnet 40 | dash.port=10332 41 | 42 | # Didn't find any certain info. We will adjust this during tests and 43 | # further development. For now lets use https://asfi.co/ recommendation. 44 | # At 2018-07-02 this was 2566 satoshis per kilobyte or 2.5 satoshi per 45 | # byte. 46 | dash.feeperunit=4 47 | 48 | [Ethereum] 49 | ethereum.disable=false 50 | ethereum.minconfirmations=1 51 | ethereum.syncdelay=5 52 | ethereum.host=ethereum.mainnet 53 | ethereum.port=11332 54 | 55 | [Litecoin] 56 | litecoin.disable=false 57 | litecoin.minconfirmations=1 58 | litecoin.syncdelay=5 59 | litecoin.host=litecoin.mainnet 60 | litecoin.port=12332 61 | 62 | # Didn't find any certain info. We will adjust this during tests and 63 | # further development. For now lets take big enough using 64 | # https://jochen-hoenicke.de/queue/#4,24h to be more than mosts. 65 | # At 2018-07-02 this was 200. 66 | litecoin.feeperunit=200 67 | 68 | [Bitcoinlightning] 69 | bitcoinlightning.disable=false 70 | bitcoinlightning.tlscertpath=/root/.lnd/tls.cert 71 | bitcoinlightning.macaroonpath=/root/.lnd/data/chain/bitcoin/mainnet/admin.macaroon 72 | # lnd RPC address 73 | bitcoinlightning.host=bitcoin-lightning.mainnet 74 | bitcoinlightning.port=10009 75 | # lnd P2P address 76 | bitcoinlightning.peerhost=connector.bitlum.io 77 | bitcoinlightning.peerport=97350 -------------------------------------------------------------------------------- /docker/mainnet/connector/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make connector data persistent. 4 | DATA_DIR=/root/.connector 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/connector.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [ ! -d $DATA_DIR ]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp $DEFAULTS_DIR/connector.conf $CONFIG 22 | 23 | 24 | # We are using `exec` to enable gracefull shutdown of running daemon. 25 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 26 | exec connector --config /root/.connector/connector.conf \ 27 | --bitcoin.user=$BITCOIN_RPC_USER \ 28 | --bitcoin.password=$BITCOIN_RPC_PASSWORD \ 29 | --bitcoin.forcelasthash=$CONNECTOR_BITCOIN_FORCE_HASH \ 30 | --bitcoincash.user=$BITCOIN_CASH_RPC_USER \ 31 | --bitcoincash.password=$BITCOIN_CASH_RPC_PASSWORD \ 32 | --bitcoincash.forcelasthash=$CONNECTOR_BITCOIN_CASH_FORCE_HASH \ 33 | --dash.user=$DASH_RPC_USER \ 34 | --dash.password=$DASH_RPC_PASSWORD \ 35 | --dash.forcelasthash=$CONNECTOR_DASH_FORCE_HASH \ 36 | --litecoin.user=$LITECOIN_RPC_USER \ 37 | --litecoin.password=$LITECOIN_RPC_PASSWORD \ 38 | --litecoin.forcelasthash=$CONNECTOR_LITECOIN_FORCE_HASH \ 39 | --ethereum.forcelasthash=$CONNECTOR_ETHEREUM_FORCE_HASH -------------------------------------------------------------------------------- /docker/mainnet/dash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG DASH_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl -L https://github.com/dashpay/dash/releases/download/v$DASH_VERSION/dashcore-${DASH_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/dashd \ 13 | */bin/dash-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 10332 21 | 22 | # P2P port. 23 | EXPOSE 10333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder dashd dash-cli /usr/local/bin/ 27 | 28 | # Default config used to initalize datadir volume at first or 29 | # cleaned deploy. It will be restored and used after each restart. 30 | COPY dash.mainnet.conf /root/default/dash.conf 31 | 32 | # Entrypoint script used to init datadir if required and for 33 | # starting dash daemon. 34 | COPY entrypoint.sh /root/ 35 | 36 | # We are using exec syntax to enable gracefull shutdown. Check 37 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 38 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/dash/dash.mainnet.conf: -------------------------------------------------------------------------------- 1 | port=10333 2 | rpcport=10332 3 | 4 | # Allow RPC for connector docker. 5 | rpcallowip=172.100.2.100 6 | 7 | txindex=1 8 | 9 | printtoconsole=1 10 | 11 | listen=1 12 | server=1 -------------------------------------------------------------------------------- /docker/mainnet/dash/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.dashcore 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/dash.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/dash.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec dashd $EXTERNAL_IP_OPT \ 33 | --rpcauth=$DASH_RPC_AUTH -------------------------------------------------------------------------------- /docker/mainnet/ethereum/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG ETHEREUM_REVISION 4 | 5 | WORKDIR /ethereum 6 | 7 | RUN curl -L https://github.com/bitlum/go-ethereum/archive/$ETHEREUM_REVISION.tar.gz \ 8 | | tar xz --strip 1 9 | 10 | RUN make geth 11 | 12 | 13 | 14 | FROM ubuntu:18.04 15 | 16 | # RPC port. 17 | EXPOSE 11332 18 | 19 | # RPC-WS port. 20 | EXPOSE 11331 21 | 22 | # P2P port. 23 | EXPOSE 30303 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder /ethereum/build/bin/geth /usr/local/bin/ 27 | 28 | # Default config and genesis used to initalize datadir volume 29 | # at first or cleaned deploy. 30 | COPY ethereum.mainnet.conf /root/default/ethereum.conf 31 | 32 | # Entrypoint script used to init datadir if required and for 33 | # starting bitcoin daemon. 34 | COPY entrypoint.sh /root/ 35 | 36 | # We are using exec syntax to enable gracefull shutdown. Check 37 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 38 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/ethereum/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This directory is expected to be volume to make blockchain and account 4 | # data persistent. 5 | DATA_DIR=/root/.ethereum 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/ethereum.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/ethereum.conf $CONFIG 23 | 24 | # At first deploy datadir keystore path should not exists which means 25 | # that account is not created, so we are creating it. 26 | KEYSTORE=$DATA_DIR/keystore 27 | if [ ! -d $KEYSTORE ] || [ ! "$(ls -A $KEYSTORE)" ]; then 28 | echo "Creating new account" 29 | geth --datadir $DATA_DIR --config $CONFIG account new --password /dev/null 30 | fi 31 | 32 | # If external IP defined when we need to set corresponding run option 33 | if [ ! -z "$EXTERNAL_IP" ]; then 34 | echo "Setting external IP" 35 | EXTERNAL_IP_OPT="--nat extip:$EXTERNAL_IP" 36 | fi 37 | 38 | # We are using `exec` to enable gracefull shutdown of running daemon. 39 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 40 | exec geth \ 41 | --debug \ 42 | --verbosity=4 \ 43 | --datadir $DATA_DIR \ 44 | --config $CONFIG \ 45 | --cache 4086 \ 46 | $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/mainnet/ethereum/ethereum.mainnet.conf: -------------------------------------------------------------------------------- 1 | [Eth] 2 | NetworkId = 1 3 | SyncMode = "fast" 4 | NoPruning = false 5 | LightPeers = 100 6 | DatabaseCache = 512 7 | TrieCleanCache = 256 8 | TrieDirtyCache = 256 9 | TrieTimeout = 3600000000000 10 | MinerGasFloor = 8000000 11 | MinerGasCeil = 8000000 12 | MinerGasPrice = 1000000000 13 | MinerRecommit = 3000000000 14 | MinerNoverify = false 15 | EnablePreimageRecording = false 16 | EWASMInterpreter = "" 17 | EVMInterpreter = "" 18 | 19 | [Eth.Ethash] 20 | CacheDir = "ethash" 21 | CachesInMem = 2 22 | CachesOnDisk = 3 23 | DatasetDir = "/root/.ethereum/ethash-dataset" 24 | DatasetsInMem = 1 25 | DatasetsOnDisk = 2 26 | PowMode = 0 27 | 28 | [Eth.TxPool] 29 | Locals = [] 30 | NoLocals = false 31 | Journal = "transactions.rlp" 32 | Rejournal = 3600000000000 33 | PriceLimit = 1 34 | PriceBump = 10 35 | AccountSlots = 16 36 | GlobalSlots = 4096 37 | AccountQueue = 64 38 | GlobalQueue = 1024 39 | Lifetime = 10800000000000 40 | 41 | [Eth.GPO] 42 | Blocks = 20 43 | Percentile = 60 44 | 45 | [Shh] 46 | MaxMessageSize = 1048576 47 | MinimumAcceptedPOW = 2e-01 48 | RestrictConnectionBetweenLightClients = true 49 | 50 | [Node] 51 | DataDir = "/root/.ethereum" 52 | 53 | HTTPHost = "0.0.0.0" 54 | HTTPPort = 11332 55 | HTTPModules = ["net", "web3", "eth", "shh", "personal"] 56 | HTTPVirtualHosts = ["ethereum.mainnet"] 57 | 58 | # Disable USB 59 | NoUSB = true 60 | 61 | # Disable WS 62 | WSHost = "" 63 | 64 | [Node.P2P] 65 | MaxPeers = 25 66 | NoDiscovery = false 67 | 68 | BootstrapNodes = [ 69 | "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", 70 | "enode://3f1d12044546b76342d59d4a05532c14b85aa669704bfe1f864fe079415aa2c02d743e03218e57a33fb94523adb54032871a6c51b2cc5514cb7c7e35b3ed0a99@13.93.211.84:30303", 71 | "enode://78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d@191.235.84.50:30303", 72 | "enode://158f8aab45f6d19c6cbf4a089c2670541a8da11978a2f90dbf6a502a4a3bab80d288afdbeb7ec0ef6d92de563767f3b1ea9e8e334ca711e9f8e2df5a0385e8e6@13.75.154.138:30303", 73 | "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", 74 | "enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303", 75 | ] 76 | 77 | BootstrapNodesV5 = [ 78 | "enode://0cc5f5ffb5d9098c8b8c62325f3797f56509bff942704687b6530992ac706e2cb946b90a34f1f19548cd3c7baccbcaea354531e5983c7d1bc0dee16ce4b6440b@40.118.3.223:30305", 79 | "enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30308", 80 | "enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30309", 81 | ] 82 | 83 | StaticNodes = [] 84 | TrustedNodes = [] 85 | ListenAddr = ":30303" 86 | EnableMsgEvents = false 87 | 88 | [Node.HTTPTimeouts] 89 | ReadTimeout = 30000000000 90 | WriteTimeout = 30000000000 91 | IdleTimeout = 120000000000 -------------------------------------------------------------------------------- /docker/mainnet/fluentd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fluent/fluentd:stable 2 | 3 | # Install required fluentd plugins 4 | RUN gem install fluent-plugin-concat \ 5 | fluent-plugin-rewrite-tag-filter \ 6 | fluent-plugin-elasticsearch \ 7 | fluent-plugin-prometheus --no-rdoc --no-ri 8 | 9 | # Copy fluentd configuration file to place where fluentd expect to have it 10 | COPY ./fluent.conf /fluentd/etc/fluent.conf 11 | 12 | # In order to add verboce tracing to fluentd daemon you need to rewrite 13 | # docker CMD directive with and add "-vv" flag. -------------------------------------------------------------------------------- /docker/mainnet/fluentd/fluent.conf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Setup fluentd to accept docker logs on tcp port 3 | # ------------------------------------------------------------------------------ 4 | 5 | 6 | @type forward 7 | bind 0.0.0.0 8 | port 24224 9 | 10 | 11 | # ------------------------------------------------------------------------------ 12 | # Concatenate logs which where spited by docker daemon. As far as in this 13 | # containers is used one logger, the format is the same - date at the start, 14 | # which we use us indicator for merging logs back in one string. 15 | # ------------------------------------------------------------------------------ 16 | 17 | 18 | @type concat 19 | key log 20 | multiline_start_regexp /^20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ 21 | 22 | 23 | 24 | @type concat 25 | key log 26 | multiline_start_regexp /^20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ 27 | 28 | 29 | 30 | @type concat 31 | key log 32 | multiline_start_regexp /^20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]/ 33 | 34 | 35 | # ------------------------------------------------------------------------------ 36 | # Events parsing to get additional fields from string. 37 | # Use filter to replace field "log" of fluentd log entry with time, level, 38 | # subsystem, message. 39 | # ------------------------------------------------------------------------------ 40 | 41 | 42 | @type parser 43 | format /(? 46 | 47 | 48 | @type parser 49 | format /(? 52 | 53 | 54 | @type parser 55 | format /(? 58 | 59 | # ------------------------------------------------------------------------------ 60 | # Sending logs to storage 61 | # ------------------------------------------------------------------------------ 62 | 63 | # Send all unparsed logs to elasticsearch too 64 | 65 | @type elasticsearch 66 | host 10.135.95.194 67 | port 9200 68 | logstash_format true 69 | logstash_prefix fluentd 70 | logstash_dateformat %Y%m%d 71 | include_tag_key true 72 | tag_key @log_name 73 | 74 | chunk_limit_size 32MB 75 | total_limit_size 2GB 76 | flush_mode interval 77 | flush_interval 5s 78 | flush_thread_count 2 79 | 80 | 81 | -------------------------------------------------------------------------------- /docker/mainnet/litecoin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG LITECOIN_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://download.litecoin.org/litecoin-$LITECOIN_VERSION/linux/litecoin-${LITECOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/litecoind \ 13 | */bin/litecoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 12332 21 | 22 | # P2P port. 23 | EXPOSE 12333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder litecoind litecoin-cli /usr/local/bin/ 27 | 28 | # Default config used to initalize datadir volume 29 | # at first or cleaned deploy. 30 | COPY litecoin.mainnet.conf /root/default/litecoin.conf 31 | 32 | # Entrypoint script used to init datadir if required and for 33 | # starting litecoin daemon. 34 | COPY entrypoint.sh /root/ 35 | 36 | # We are using exec syntax to enable gracefull shutdown. Check 37 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 38 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/mainnet/litecoin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.litecoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/litecoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/litecoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec litecoind $EXTERNAL_IP_OPT \ 33 | --rpcauth=$LITECOIN_RPC_AUTH -------------------------------------------------------------------------------- /docker/mainnet/litecoin/litecoin.mainnet.conf: -------------------------------------------------------------------------------- 1 | port=12333 2 | 3 | rpcbind=0.0.0.0 4 | rpcport=12332 5 | 6 | rpcallowip=172.100.2.100 7 | 8 | addresstype=legacy 9 | 10 | txindex=1 11 | 12 | printtoconsole=1 13 | 14 | discover=1 15 | listen=1 16 | server=1 -------------------------------------------------------------------------------- /docker/mainnet/logrotate.conf: -------------------------------------------------------------------------------- 1 | # This is rsyslog configuration for mainnet connector dockers' host machine. 2 | 3 | # It intended to be placed at /etc/logrotate.d/connector during deploy 4 | # following with restarting logrotate. 5 | 6 | /var/log/connector/*.log { 7 | # Rotate logs daily. 8 | daily 9 | 10 | # Keep last 30 days. 11 | rotate 180 12 | 13 | # If the log file is missing, go on to the next one without issuing an 14 | # error message. 15 | missingok 16 | 17 | # Do not rotate the log if it is empty. 18 | notifempty 19 | 20 | # Postpone compression of the previous log file to the next rotation cycle. 21 | delaycompress 22 | 23 | # Old versions of log files are compressed with gzip(1). 24 | compress 25 | 26 | # Signal rsyslog about rotation to start new log file. 27 | postrotate 28 | invoke-rc.d rsyslog rotate > /dev/null 29 | endscript 30 | } -------------------------------------------------------------------------------- /docker/mainnet/rsyslog.conf: -------------------------------------------------------------------------------- 1 | # This is rsyslog configuration for mainnet connector dockers' host machine. 2 | 3 | # It intended to be placed at /etc/rsyslog.d/connector.conf during deploy 4 | # following with rsyslogd restarting. 5 | 6 | # Fix `\t` and other caharacters in log messages. 7 | global( 8 | parser.escapecontrolcharactertab="off" 9 | ) 10 | 11 | # Raw log line message template 12 | template(name="outfmt" type="list") { 13 | property(name="msg" position.from="2") 14 | constant(value="\n") 15 | } 16 | 17 | # Template for log dynamic file name to put containers logs in separate 18 | # file in `/var/log/connector/`. E.g. for dash container log file is 19 | # `/var/log/connector/dash.simnet.primary.log`. 20 | template(name="dynaFile" type="list") { 21 | constant(value="/var/log/connector/") 22 | property( 23 | name="syslogtag" 24 | # We parsing syslogtag like `connector-docker/dash.simnet.perimary[123]` 25 | # to get `dash.simnet.perimary` substring. 26 | regex.expression="^connector-docker\\/\\(.\\+\\)\\[" 27 | regex.submatch="1" 28 | ) 29 | constant(value=".log") 30 | } 31 | 32 | # Put connector docker containers' logs in separate files and discard whem. 33 | if ($programname == "connector-docker") then { 34 | action(type="omfile" dynaFile="dynaFile" template="outfmt") 35 | stop 36 | } 37 | -------------------------------------------------------------------------------- /docker/simnet/.env: -------------------------------------------------------------------------------- 1 | # This file contains list of environment variables required to deploy 2 | # simnet connector dockers. 3 | 4 | # *_VERSION variables should contain version in N.M.O format. This 5 | # variables are used to construct download link from official sources 6 | # of compiled binaries. So you should be sure that version you specifing 7 | # can be download. Check corresponding `Dockerfile` to get exact download 8 | # link. 9 | 10 | # *_REVISION variables specifies revision or branch which will be used. 11 | # As *_VERSION variables they are used to construct download link of 12 | # source code archive. 13 | # We encourage you to use revision a.k.a commit hash and do not use 14 | # branch name because docker-compose has cache and after first deploy 15 | # downloaded branch is freezed and further deploys will not download 16 | # new branch revisions. In contrast commit specifies exact 17 | # revision of source code so cache is working as should. 18 | 19 | # This variable intended to be specified outside of .env file. 20 | # You should specify them during deploy on specific droplet. 21 | # PRIVATE_IP=10.135.63.178 22 | 23 | BITCOIN_VERSION=0.17.0 24 | 25 | BITCOIN_CASH_VERSION=0.18.2 26 | 27 | BITCOIN_LIGHTNING_REVISION=c7ca387a9d92a1a2cd0f6e56b61ff04b9adc062d 28 | 29 | DASH_VERSION=0.12.3.3 30 | 31 | ETHEREUM_REVISION=4884b01a2bdfaf1dd96070a628d029ea9c854448 32 | 33 | LITECOIN_VERSION=0.16.3 -------------------------------------------------------------------------------- /docker/simnet/bitcoin-cash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_CASH_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://download.bitcoinabc.org/$BITCOIN_CASH_VERSION/linux/bitcoin-abc-${BITCOIN_CASH_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/bitcoind \ 13 | */bin/bitcoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # ROLE is bitcoin node role: primary or secondary. 20 | # 21 | # Primary role means that this node will init new blockchain if it not 22 | # exists during deploy or restart. 23 | # 24 | # Secondary rank means that this node will try to connect to primary 25 | # node and use blockchain of latter. 26 | ARG ROLE 27 | 28 | # RPC port. 29 | EXPOSE 9332 30 | 31 | # P2P port. 32 | EXPOSE 9333 33 | 34 | # Copying required binaries from builder stage. 35 | COPY --from=builder bitcoind /usr/local/bin/bitcoin-cashd 36 | COPY --from=builder bitcoin-cli /usr/local/bin/bitcoin-cash-cli 37 | 38 | # Default config used to initalize datadir volume at first or 39 | # cleaned deploy. 40 | COPY bitcoin.simnet.$ROLE.conf /root/default/bitcoin.conf 41 | 42 | # Entrypoint script used to init datadir if required and for 43 | # starting bitcoin cash daemon. 44 | COPY entrypoint.sh /root/ 45 | 46 | # We are using exec syntax to enable gracefull shutdown. Check 47 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 48 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/bitcoin-cash/bitcoin.simnet.primary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=9333 4 | 5 | rpcbind=0.0.0.0 6 | rpcport=9332 7 | 8 | rpcallowip=0.0.0.0/0 9 | rpcuser=user 10 | rpcpassword=password 11 | 12 | dnsseed=0 13 | upnp=0 14 | 15 | txindex=1 16 | usecashaddr=0 17 | 18 | printtoconsole=1 -------------------------------------------------------------------------------- /docker/simnet/bitcoin-cash/bitcoin.simnet.secondary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=9333 4 | 5 | rpcbind=0.0.0.0 6 | rpcport=9332 7 | 8 | rpcallowip=0.0.0.0/0 9 | rpcuser=user 10 | rpcpassword=password 11 | 12 | dnsseed=0 13 | upnp=0 14 | 15 | txindex=1 16 | usecashaddr=0 17 | 18 | printtoconsole=1 19 | 20 | connect=bitcoin-cash.simnet.primary:9333 -------------------------------------------------------------------------------- /docker/simnet/bitcoin-cash/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.bitcoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/bitcoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/bitcoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | # We are using `exec` to enable gracefull shutdown of running daemon. 30 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 31 | exec bitcoin-cashd $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/bitcoin-lightning-helper/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG BITCOIN_LIGHTNING_REVISION 4 | 5 | RUN go get -u github.com/golang/dep/cmd/dep 6 | 7 | WORKDIR $GOPATH/src/github.com/lightningnetwork/lnd 8 | 9 | # Instead of cloning lightningnetwork/lnd temproray use ourw version of lnd 10 | # daemon, but put in lightningnetwork/lnd directory so that all installation 11 | # scripts could work without being changed. 12 | RUN git clone https://github.com/bitlum/lnd.git . 13 | 14 | RUN git checkout $BITCOIN_LIGHTNING_REVISION 15 | 16 | RUN dep ensure -v 17 | 18 | RUN make install 19 | 20 | 21 | 22 | FROM python:2.7 23 | 24 | EXPOSE 80 25 | 26 | # Copying required binaries from builder stage. 27 | COPY --from=builder /go/bin/lncli /usr/local/bin/ 28 | 29 | RUN pip install --no-cache-dir Flask 30 | 31 | COPY http-server.py / 32 | 33 | # We are using exec syntax to enable gracefull shutdown. Check 34 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 35 | ENTRYPOINT ["python", "http-server.py"] -------------------------------------------------------------------------------- /docker/simnet/bitcoin-lightning-helper/http-server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request 3 | from subprocess import Popen, PIPE 4 | 5 | app = Flask(__name__) 6 | 7 | rpcAddr = "bitcoin-lightning.simnet.primary:10009" 8 | 9 | macaroonFile = "/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon" 10 | tlsCertFile = "/root/.lnd/tls.cert" 11 | 12 | @app.route('/pay_invoice') 13 | def pay_invoice(): 14 | invoice = request.args.get('invoice') 15 | 16 | bashCommand = "lncli --network=regtest --rpcserver={} --macaroonpath={} " \ 17 | "--tlscertpath={} payinvoice --force --pay_req={}".format( \ 18 | rpcAddr, macaroonFile, tlsCertFile, invoice) 19 | 20 | print(bashCommand) 21 | p = Popen(bashCommand, shell=True, stdout=PIPE) 22 | out = p.communicate()[0] 23 | return str(out) 24 | 25 | @app.route('/generate_invoice') 26 | def generate_invoice(): 27 | amount = request.args.get('amount') 28 | 29 | bashCommand = "lncli --network=regtest --rpcserver={} --macaroonpath={} " \ 30 | "--tlscertpath={} addinvoice --amt={}".format( \ 31 | rpcAddr, macaroonFile, tlsCertFile, amount) 32 | 33 | print(bashCommand) 34 | p = Popen(bashCommand, shell=True, stdout=PIPE) 35 | out = p.communicate()[0] 36 | return str(out) 37 | 38 | if __name__ == '__main__': 39 | app.run(host="0.0.0.0", port="80") 40 | 41 | 42 | -------------------------------------------------------------------------------- /docker/simnet/bitcoin-lightning/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG BITCOIN_LIGHTNING_REVISION 4 | 5 | RUN go get -u github.com/golang/dep/cmd/dep 6 | 7 | WORKDIR $GOPATH/src/github.com/lightningnetwork/lnd 8 | 9 | # Instead of cloning lightningnetwork/lnd temproray use ourw version of lnd 10 | # daemon, but put in lightningnetwork/lnd directory so that all installation 11 | # scripts could work without being changed. 12 | RUN git clone https://github.com/bitlum/lnd.git . 13 | 14 | RUN git checkout $BITCOIN_LIGHTNING_REVISION 15 | 16 | RUN dep ensure -v 17 | 18 | RUN make install 19 | 20 | 21 | 22 | FROM ubuntu:18.04 23 | 24 | # ROLE is bitcoin node role: primary or secondary. 25 | # 26 | # Primary role means that this node will init new blockchain if it not 27 | # exists during deploy or restart. 28 | # 29 | # Secondary rank means that this node will try to connect to primary 30 | # node and use blockchain of latter. 31 | ARG ROLE 32 | 33 | # P2P port. 34 | EXPOSE 9735 35 | 36 | # RPC port. 37 | EXPOSE 10009 38 | 39 | # Copying required binaries from builder stage. 40 | COPY --from=builder /go/bin/lnd /go/bin/lncli /usr/local/bin/ 41 | 42 | # Default config used to initalize datadir volume at first or 43 | # cleaned deploy. 44 | COPY bitcoin-lightning.simnet.$ROLE.conf /root/default/lnd.conf 45 | 46 | # Entrypoint script used to init datadir if required and for 47 | # starting bitcoin daemon. 48 | COPY entrypoint.sh /root/ 49 | 50 | # We are using exec syntax to enable gracefull shutdown. Check 51 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 52 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/bitcoin-lightning/bitcoin-lightning.simnet.primary.conf: -------------------------------------------------------------------------------- 1 | [Application Options] 2 | 3 | maxpendingchannels=10 4 | 5 | listen=0.0.0.0:9735 6 | rpclisten=0.0.0.0:10009 7 | 8 | noseedbackup=1 9 | 10 | tlsextradomain=bitcoin-lightning.simnet.primary 11 | externalip=bitcoin-lightning.simnet.primary:9735 12 | debuglevel=trace 13 | 14 | [Bitcoin] 15 | bitcoin.active=1 16 | bitcoin.regtest=1 17 | bitcoin.node=bitcoind 18 | 19 | [Bitcoind] 20 | bitcoind.rpchost=bitcoin.simnet.primary:8332 21 | bitcoind.rpcuser=user 22 | bitcoind.rpcpass=password 23 | bitcoind.zmqpubrawblock=tcp://bitcoin.simnet.primary:8334 24 | bitcoind.zmqpubrawtx=tcp://bitcoin.simnet.primary:8335 -------------------------------------------------------------------------------- /docker/simnet/bitcoin-lightning/bitcoin-lightning.simnet.secondary.conf: -------------------------------------------------------------------------------- 1 | [Application Options] 2 | 3 | maxpendingchannels=10 4 | 5 | listen=0.0.0.0:9735 6 | rpclisten=0.0.0.0:10009 7 | 8 | noseedbackup=1 9 | 10 | tlsextradomain=bitcoin-lightning.simnet.secondary 11 | externalip=bitcoin-lightning.simnet.secondary:9735 12 | debuglevel=trace 13 | 14 | [Bitcoin] 15 | bitcoin.active=1 16 | bitcoin.regtest=1 17 | bitcoin.node=bitcoind 18 | 19 | [Bitcoind] 20 | bitcoind.rpchost=bitcoin.simnet.secondary:8332 21 | bitcoind.rpcuser=user 22 | bitcoind.rpcpass=password 23 | bitcoind.zmqpubrawblock=tcp://bitcoin.simnet.primary:8334 24 | bitcoind.zmqpubrawtx=tcp://bitcoin.simnet.primary:8335 -------------------------------------------------------------------------------- /docker/simnet/bitcoin-lightning/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make lnd data persistent. 4 | DATA_DIR=/root/.lnd 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/lnd.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [ ! -d $DATA_DIR ]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp $DEFAULTS_DIR/lnd.conf $CONFIG 22 | 23 | # If external IP defined we need to set corresponding run option. 24 | if [ ! -z $EXTERNAL_IP ]; then 25 | EXTERNAL_IP_OPT="--externalip=$EXTERNAL_IP" 26 | fi 27 | 28 | # We are using `exec` to enable gracefull shutdown of running daemon. 29 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 30 | exec lnd $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/bitcoin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://bitcoin.org/bin/bitcoin-core-$BITCOIN_VERSION/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/bitcoind \ 13 | */bin/bitcoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # ROLE is bitcoin node role: primary or secondary. 20 | # 21 | # Primary role means that this node will init new blockchain if it not 22 | # exists during deploy or restart. 23 | # 24 | # Secondary rank means that this node will try to connect to primary 25 | # node and use blockchain of latter. 26 | ARG ROLE 27 | 28 | # RPC port. 29 | EXPOSE 8332 30 | 31 | # P2P port. 32 | EXPOSE 8333 33 | 34 | # `zmqpubrawblock` and `zmqpubrawtx` port. 35 | EXPOSE 8334 36 | 37 | # Copying required binaries from builder stage. 38 | COPY --from=builder bitcoind bitcoin-cli /usr/local/bin/ 39 | 40 | # Default config used to initalize datadir volume at first or 41 | # cleaned deploy. 42 | COPY bitcoin.simnet.$ROLE.conf /root/default/bitcoin.conf 43 | 44 | # Entrypoint script used to init datadir if required and for 45 | # starting bitcoin daemon. 46 | COPY entrypoint.sh /root/ 47 | 48 | # We are using exec syntax to enable gracefull shutdown. Check 49 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 50 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/bitcoin/bitcoin.simnet.primary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | [regtest] 4 | port=8333 5 | rpcbind=0.0.0.0 6 | rpcport=8332 7 | rpcallowip=0.0.0.0/0 8 | rpcuser=user 9 | rpcpassword=password 10 | 11 | dnsseed=0 12 | upnp=0 13 | 14 | txindex=1 15 | 16 | printtoconsole=1 17 | 18 | zmqpubrawblock=tcp://0.0.0.0:8334 19 | zmqpubrawtx=tcp://0.0.0.0:8335 20 | 21 | # getaddressesbyaccount is deprecated and will be removed in V0.18. 22 | # but we need it right now. 23 | deprecatedrpc=accounts 24 | deprecatedrpc=signrawtransaction -------------------------------------------------------------------------------- /docker/simnet/bitcoin/bitcoin.simnet.secondary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=8333 4 | 5 | [regtest] 6 | rpcbind=0.0.0.0 7 | rpcport=8332 8 | rpcallowip=0.0.0.0/0 9 | rpcuser=user 10 | rpcpassword=password 11 | 12 | dnsseed=0 13 | upnp=0 14 | 15 | txindex=1 16 | 17 | printtoconsole=1 18 | 19 | zmqpubrawblock=tcp://0.0.0.0:8334 20 | zmqpubrawtx=tcp://0.0.0.0:8335 21 | 22 | connect=bitcoin.simnet.primary:8333 23 | 24 | # getaddressesbyaccount is deprecated and will be removed in V0.18. 25 | # but we need it right now. 26 | deprecatedrpc=accounts 27 | deprecatedrpc=signrawtransaction -------------------------------------------------------------------------------- /docker/simnet/bitcoin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.bitcoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/bitcoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/bitcoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | # We are using `exec` to enable gracefull shutdown of running daemon. 30 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 31 | exec bitcoind $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/blocks-generator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_VERSION 4 | ARG BITCOIN_CASH_VERSION 5 | ARG DASH_VERSION 6 | ARG LITECOIN_VERSION 7 | 8 | RUN apt-get update && apt-get install -y \ 9 | ca-certificates \ 10 | curl \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | RUN curl https://bitcoin.org/bin/bitcoin-core-$BITCOIN_VERSION/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 14 | | tar xz --wildcards --strip 2 \ 15 | */bin/bitcoin-cli 16 | 17 | RUN mkdir /bitcoin-cash 18 | RUN curl https://download.bitcoinabc.org/$BITCOIN_CASH_VERSION/linux/bitcoin-abc-${BITCOIN_CASH_VERSION}-x86_64-linux-gnu.tar.gz \ 19 | | tar xz -C /bitcoin-cash --wildcards --strip 2 \ 20 | */bin/bitcoin-cli 21 | 22 | RUN curl -L https://github.com/dashpay/dash/releases/download/v$DASH_VERSION/dashcore-${DASH_VERSION}-x86_64-linux-gnu.tar.gz \ 23 | | tar xz --wildcards --strip 2 \ 24 | */bin/dash-cli 25 | 26 | RUN curl https://download.litecoin.org/litecoin-$LITECOIN_VERSION/linux/litecoin-${LITECOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 27 | | tar xz --wildcards --strip 2 \ 28 | */bin/litecoin-cli 29 | 30 | 31 | FROM ubuntu:18.04 32 | 33 | # Blocks generation period in seconds 34 | ARG PERIOD=10 35 | 36 | # Setting runtime environment variables whhich will be used in `entrypoint.sh` 37 | ENV PERIOD $PERIOD 38 | 39 | # Copying CLI binaries from already builded blockchain containers. They 40 | # should be builded already because this container depends on them. 41 | COPY --from=builder bitcoin-cli /usr/local/bin/ 42 | COPY --from=builder bitcoin-cash/bitcoin-cli /usr/local/bin/bitcoin-cash-cli 43 | COPY --from=builder dash-cli /usr/local/bin/ 44 | COPY --from=builder litecoin-cli /usr/local/bin/ 45 | 46 | # Entrypoint script used for periodically blocks generation 47 | COPY entrypoint.sh /root/ 48 | 49 | # We are using exec syntax to enable gracefull shutdown. Check 50 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 51 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/blocks-generator/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RPC_USER="user" 4 | RPC_PASSWORD="password" 5 | 6 | BTC_RPC_HOST="bitcoin.simnet.primary" 7 | BTC_RPC_PORT=8332 8 | BTC_OPTS="\ 9 | --rpcconnect=$BTC_RPC_HOST --rpcport=$BTC_RPC_PORT \ 10 | --rpcuser=$RPC_USER --rpcpassword=$RPC_PASSWORD \ 11 | --regtest" 12 | 13 | BCH_RPC_HOST="bitcoin-cash.simnet.primary" 14 | BCH_RPC_PORT=9332 15 | BCH_OPTS="\ 16 | --rpcconnect=$BCH_RPC_HOST --rpcport=$BCH_RPC_PORT \ 17 | --rpcuser=$RPC_USER --rpcpassword=$RPC_PASSWORD \ 18 | --regtest" 19 | 20 | DASH_RPC_HOST="dash.simnet.primary" 21 | DASH_RPC_PORT=10332 22 | DASH_OPTS="\ 23 | --rpcconnect=$DASH_RPC_HOST --rpcport=$DASH_RPC_PORT \ 24 | --rpcuser=$RPC_USER --rpcpassword=$RPC_PASSWORD \ 25 | --regtest" 26 | 27 | LTC_RPC_HOST="litecoin.simnet.primary" 28 | LTC_RPC_PORT=12332 29 | LTC_OPTS="\ 30 | --rpcconnect=$LTC_RPC_HOST --rpcport=$LTC_RPC_PORT \ 31 | --rpcuser=$RPC_USER --rpcpassword=$RPC_PASSWORD \ 32 | --regtest" 33 | 34 | # We need to wait until all primary blockchain nodes are started. 35 | echo "$(date '+%Y-%m-%d %H:%M:%S') Waiting for all primary blockchains nodes are started" 36 | sleep 60 37 | 38 | # Initial blocks generation. 39 | echo "$(date '+%Y-%m-%d %H:%M:%S') Initial blocks generation" 40 | bitcoin-cli $BTC_OPTS generate 400 41 | bitcoin-cash-cli $BCH_OPTS generate 100 42 | dash-cli $DASH_OPTS generate 100 43 | litecoin-cli $LTC_OPTS generate 100 44 | 45 | # Proper way to catch shutdown signal and stop infinite loop. In this 46 | # solution we consider blockchains' cli runs are not long running process 47 | # so we need to stop sleep only. 48 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 49 | shutdown() { 50 | kill -s SIGTERM $! 51 | exit 0 52 | } 53 | trap shutdown SIGINT SIGTERM 54 | 55 | # Periodically block generation. 56 | while true 57 | do 58 | echo "$(date '+%Y-%m-%d %H:%M:%S') Periodical blocks generation" 59 | bitcoin-cli $BTC_OPTS generate 1 60 | bitcoin-cash-cli $BCH_OPTS generate 1 61 | dash-cli $DASH_OPTS generate 1 62 | litecoin-cli $LTC_OPTS generate 1 63 | 64 | # Wait for next period. 65 | sleep $PERIOD & 66 | wait $! 67 | done -------------------------------------------------------------------------------- /docker/simnet/connector/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | ca-certificates \ 5 | curl \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | # This implies that service has to be built locally first, and putted 9 | # in the docker directory, for running docker build. 10 | COPY bin/connector /usr/local/bin 11 | COPY bin/pscli /usr/local/bin 12 | 13 | # Default config used to initalize datadir volume at first or 14 | # cleaned deploy. 15 | COPY connector.simnet.conf /root/default/connector.conf 16 | 17 | # Entrypoint script used to init datadir if required and for 18 | # starting dash daemon 19 | COPY entrypoint.sh /root/ 20 | 21 | # We are using exec syntax to enable gracefull shutdown. Check 22 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 23 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/connector/connector.simnet.conf: -------------------------------------------------------------------------------- 1 | [Application Options] 2 | datadir=/root/.connector 3 | debuglevel=trace 4 | network=simnet 5 | 6 | # RPC address to bind 7 | rpchost=0.0.0.0 8 | rpcport=9002 9 | 10 | [Bitcoin] 11 | bitcoin.disable=false 12 | bitcoin.minconfirmations=1 13 | bitcoin.syncdelay=5 14 | bitcoin.host=bitcoin.simnet.secondary 15 | bitcoin.port=8332 16 | bitcoin.user=user 17 | bitcoin.password=password 18 | bitcoin.feeperunit=11 19 | 20 | [Bitcoincash] 21 | bitcoincash.disable=true 22 | bitcoincash.minconfirmations=1 23 | bitcoincash.syncdelay=5 24 | bitcoincash.host=bitcoin-cash.simnet.secondary 25 | bitcoincash.port=9332 26 | bitcoincash.user=user 27 | bitcoincash.password=password 28 | bitcoincash.feeperunit=11 29 | 30 | [Dash] 31 | dash.disable=true 32 | dash.minconfirmations=1 33 | dash.syncdelay=5 34 | dash.host=dash.simnet.secondary 35 | dash.port=10332 36 | dash.user=user 37 | dash.password=password 38 | dash.feeperunit=11 39 | 40 | [Ethereum] 41 | ethereum.disable=true 42 | ethereum.minconfirmations=1 43 | ethereum.syncdelay=5 44 | ethereum.host=ethereum.simnet.secondary 45 | ethereum.port=11332 46 | ethereum.password= 47 | 48 | [Litecoin] 49 | litecoin.disable=true 50 | litecoin.minconfirmations=1 51 | litecoin.syncdelay=5 52 | litecoin.host=litecoin.simnet.secondary 53 | litecoin.port=12332 54 | litecoin.user=user 55 | litecoin.password=password 56 | litecoin.feeperunit=11 57 | 58 | [Bitcoinlightning] 59 | bitcoinlightning.disable=true 60 | bitcoinlightning.tlscertpath=/root/.lnd/tls.cert 61 | bitcoinlightning.macaroonpath=/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon 62 | # lnd RPC address 63 | bitcoinlightning.host=bitcoin-lightning.simnet.secondary 64 | bitcoinlightning.port=10009 65 | # lnd P2P address 66 | bitcoinlightning.peerhost=simnet.connector.bitlum.io 67 | bitcoinlightning.peerport=9735 68 | 69 | [Prometheus] 70 | prometheus.port=9998 -------------------------------------------------------------------------------- /docker/simnet/connector/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make connector data persistent. 4 | DATA_DIR=/root/.connector 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/connector.conf 11 | 12 | # At first deploy datadir should not exists, we creating it. 13 | if [ ! -d $DATA_DIR ]; then 14 | mkdir $DATA_DIR 15 | fi 16 | 17 | # We always restoring default config shipped with docker. 18 | echo "Restoring default config" 19 | cp $DEFAULTS_DIR/connector.conf $CONFIG 20 | 21 | # We are using `exec` to enable gracefull shutdown of running daemon. 22 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 23 | exec connector --config /root/.connector/connector.conf -------------------------------------------------------------------------------- /docker/simnet/dash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG DASH_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl -L https://github.com/dashpay/dash/releases/download/v$DASH_VERSION/dashcore-${DASH_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/dashd \ 13 | */bin/dash-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # ROLE is bitcoin node role: primary or secondary. 20 | # 21 | # Primary role means that this node will init new blockchain if it not 22 | # exists during deploy or restart. 23 | # 24 | # Secondary rank means that this node will try to connect to primary 25 | # node and use blockchain of latter. 26 | ARG ROLE 27 | 28 | # RPC port. 29 | EXPOSE 10332 30 | 31 | # P2P port. 32 | EXPOSE 10333 33 | 34 | # Copying required binaries from builder stage. 35 | COPY --from=builder dashd dash-cli /usr/local/bin/ 36 | 37 | # Default config used to initalize datadir volume at first or 38 | # cleaned deploy. 39 | COPY dash.simnet.$ROLE.conf /root/default/dash.conf 40 | 41 | # Entrypoint script used to init datadir if required and for 42 | # starting dash daemon. 43 | COPY entrypoint.sh /root/ 44 | 45 | # We are using exec syntax to enable gracefull shutdown. Check 46 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 47 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/dash/dash.simnet.primary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=10333 4 | rpcport=10332 5 | 6 | rpcallowip=0.0.0.0/0 7 | rpcuser=user 8 | rpcpassword=password 9 | 10 | dnsseed=0 11 | upnp=0 12 | litemode=1 13 | 14 | txindex=1 15 | 16 | printtoconsole=1 -------------------------------------------------------------------------------- /docker/simnet/dash/dash.simnet.secondary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=10333 4 | rpcport=10332 5 | 6 | rpcallowip=0.0.0.0/0 7 | rpcuser=user 8 | rpcpassword=password 9 | 10 | dnsseed=0 11 | upnp=0 12 | litemode=1 13 | 14 | txindex=1 15 | 16 | printtoconsole=1 17 | 18 | connect=dash.simnet.primary:10333 -------------------------------------------------------------------------------- /docker/simnet/dash/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.dashcore 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/dash.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/dash.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | # We are using `exec` to enable gracefull shutdown of running daemon. 30 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 31 | exec dashd $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/ethereum-bootnode/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG ETHEREUM_REVISION 4 | 5 | WORKDIR /ethereum 6 | 7 | RUN curl -L https://github.com/bitlum/go-ethereum/archive/$ETHEREUM_REVISION.tar.gz \ 8 | | tar xz --strip 1 9 | 10 | RUN make all 11 | 12 | 13 | 14 | FROM ubuntu:18.04 15 | 16 | # P2P port 17 | EXPOSE 30301 18 | 19 | # Copying required binaries from builder stage 20 | COPY --from=builder /ethereum/build/bin/bootnode /usr/local/bin/ 21 | 22 | # Entrypoint script used to init datadir if required and for 23 | # starting bootnode daemon 24 | COPY entrypoint.sh /root/ 25 | 26 | # We are using exec syntax to enable gracefull shutdown. Check 27 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 28 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/ethereum-bootnode/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Docker container public IP 4 | HOST_ADDR=`awk 'END{print $1}' /etc/hosts` 5 | 6 | # Bootnode bind address 7 | BIND_ADDR="$HOST_ADDR:30301" 8 | 9 | # This path is expected to be volume to be able to share bootnode keys 10 | DATA_DIR=/bootnode 11 | 12 | # This file will contain bootnode key 13 | KEY_FILE=$DATA_DIR/bootnode.key 14 | 15 | # This file will contain enode URL 16 | ENODE_FILE=$DATA_DIR/enode.url 17 | 18 | # If data directory doesn't exists this means that volume is not mounted 19 | # or mounted incorrectly, so we must fail. 20 | if [ ! -d $DATA_DIR ]; then 21 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 22 | exit 1 23 | fi 24 | 25 | if [ ! -f $KEY_FILE ]; then 26 | echo "Generation bootnode key" 27 | bootnode --genkey=$KEY_FILE 28 | fi 29 | 30 | if [ ! -f $ENODE_FILE ]; then 31 | echo "Computing enode URL" 32 | ENODE_KEY=`bootnode --nodekey=$KEY_FILE -writeaddress` 33 | ENODE_URL="enode://$ENODE_KEY@$BIND_ADDR" 34 | echo "Writing enode URL to file" 35 | echo $ENODE_URL > $ENODE_FILE 36 | fi 37 | 38 | # If external IP defined when we need to set corresponding run option 39 | if [ ! -z "$EXTERNAL_IP" ]; then 40 | EXTERNAL_IP_OPT="--nat extip:$EXTERNAL_IP" 41 | fi 42 | 43 | # Start bootnode with computed key file on defined bind address and 44 | # with optional nat external IP. 45 | echo "Starting bootnode" 46 | # We are using `exec` to enable gracefull shutdown of running daemon. 47 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 48 | exec bootnode --nodekey=$KEY_FILE --addr=$BIND_ADDR $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/ethereum/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG ETHEREUM_REVISION 4 | 5 | WORKDIR /ethereum 6 | 7 | RUN curl -L https://github.com/bitlum/go-ethereum/archive/$ETHEREUM_REVISION.tar.gz \ 8 | | tar xz --strip 1 9 | 10 | RUN make geth 11 | 12 | 13 | 14 | FROM ubuntu:18.04 15 | 16 | # ROLE is bitcoin node role: primary or secondary. 17 | # 18 | # Primary role means that this node will init new blockchain if it not 19 | # exists during deploy or restart. 20 | # 21 | # Secondary rank means that this node will try to connect to primary 22 | # node and use blockchain of latter. 23 | ARG ROLE 24 | 25 | # RPC port. 26 | EXPOSE 11332 27 | 28 | # RPC-WS port. 29 | EXPOSE 11331 30 | 31 | # P2P port. 32 | EXPOSE 11333 33 | 34 | # Copying required binaries from builder stage. 35 | COPY --from=builder /ethereum/build/bin/geth /usr/local/bin/ 36 | 37 | # Default config and genesis used to initalize datadir volume 38 | # at first or cleaned deploy. 39 | COPY ethereum.simnet.$ROLE.conf /root/default/ethereum.conf 40 | COPY genesis.json /root/default/ 41 | 42 | # Entrypoint script used to init datadir if required and for 43 | # starting bitcoin daemon. 44 | COPY entrypoint.sh /root/ 45 | 46 | # We are using exec syntax to enable gracefull shutdown. Check 47 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 48 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/ethereum/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This directory is expected to be volume to make blockchain and account 4 | # data persistent. 5 | DATA_DIR=/root/.ethereum 6 | 7 | BOOTNODE_DIR=/bootnode 8 | 9 | # This path is expected to have default data used to init environment 10 | # at first deploy such as config and genesis. 11 | DEFAULTS_DIR=/root/default 12 | 13 | CONFIG=$DATA_DIR/ethereum.conf 14 | GENESIS=$DATA_DIR/genesis.conf 15 | ENODE=$BOOTNODE_DIR/enode.url 16 | 17 | # If data directory doesn't exists this means that volume is not mounted 18 | # or mounted incorrectly, so we must fail. 19 | if [ ! -d $DATA_DIR ]; then 20 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 21 | exit 1 22 | fi 23 | 24 | # If bootnode directory doesn't exists this means that volume is not 25 | # mounted or mounted incorrectly, so we must fail. 26 | if [ ! -d $BOOTNODE_DIR ]; then 27 | echo "Bootnode directory '$BOOTNODE_DIR' doesn't exists. Check your volume configuration." 28 | exit 2 29 | fi 30 | 31 | if [ ! -f $ENODE ]; then 32 | echo "Bootnode directory '$BOOTNODE_DIR' doesn't contain 'enode.url' file. Does 'ethereum-bootnode' container successfully started?" 33 | exit 3 34 | fi 35 | 36 | # We always restoring default config shipped with docker. 37 | echo "Restoring default config" 38 | cp $DEFAULTS_DIR/ethereum.conf $CONFIG 39 | 40 | # At first deploy genesis in datadir should not exists so we 41 | # copying from default genesis shipped with docker. 42 | if [ ! -f $GENESIS ]; then 43 | echo "Copying default genesis" 44 | cp $DEFAULTS_DIR/genesis.json $GENESIS 45 | fi 46 | 47 | # At first deploy datadir keystore path should not exists which means 48 | # that account is not created, so we are creating it. 49 | KEYSTORE=$DATA_DIR/keystore 50 | if [ ! -d $KEYSTORE ] || [ ! "$(ls -A $KEYSTORE)" ]; then 51 | echo "Creating new account" 52 | geth --datadir $DATA_DIR --config $CONFIG account new --password /dev/null 53 | fi 54 | 55 | # At first deploy datadir geth path should not exists which means 56 | # that blockchain is not inited, so we are initing it. 57 | GETH=$DATA_DIR/geth 58 | if [ ! -d $GETH ] || [ ! "$(ls -A $GETH)" ]; then 59 | echo "Initialising genesis block" 60 | geth --datadir $DATA_DIR --config $CONFIG init $GENESIS 61 | fi 62 | 63 | # Set mine option to enable blocks mining if required. 64 | if [ "$MINE" -eq "1" ]; then 65 | MINE_OPT="--mine" 66 | fi 67 | 68 | # If external IP defined when we need to set corresponding run option 69 | if [ ! -z "$EXTERNAL_IP" ]; then 70 | EXTERNAL_IP_OPT="--nat extip:$EXTERNAL_IP" 71 | fi 72 | 73 | # We are using `exec` to enable gracefull shutdown of running daemon. 74 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 75 | exec geth \ 76 | --datadir $DATA_DIR \ 77 | --config $CONFIG \ 78 | --bootnodes `cat $ENODE` \ 79 | $MINE_OPT \ 80 | $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/ethereum/ethereum.simnet.primary.conf: -------------------------------------------------------------------------------- 1 | # Note: this config doesn't contain the genesis block. 2 | 3 | [Eth] 4 | NetworkId = 876546875 5 | SyncMode = "fast" 6 | LightPeers = 20 7 | DatabaseCache = 128 8 | GasPrice = 18000000000 9 | EnablePreimageRecording = false 10 | 11 | [Eth.Ethash] 12 | CacheDir = "ethash" 13 | CachesInMem = 2 14 | CachesOnDisk = 3 15 | DatasetDir = "/root/.ethereum/ethash.dagdir" 16 | DatasetsInMem = 1 17 | DatasetsOnDisk = 2 18 | 19 | [Eth.TxPool] 20 | NoLocals = false 21 | Journal = "transactions.rlp" 22 | Rejournal = 3600000000000 23 | PriceLimit = 1 24 | PriceBump = 10 25 | AccountSlots = 16 26 | GlobalSlots = 4096 27 | AccountQueue = 64 28 | GlobalQueue = 1024 29 | Lifetime = 10800000000000 30 | 31 | [Eth.GPO] 32 | Blocks = 10 33 | Percentile = 50 34 | 35 | [Shh] 36 | MaxMessageSize = 1048576 37 | MinimumAcceptedPOW = 2e-01 38 | 39 | [Node] 40 | DataDir = "" 41 | NoUSB = true 42 | IPCPath = "/root/.ethereum/geth.ipc" 43 | HTTPHost = "0.0.0.0" 44 | HTTPPort = 11332 45 | HTTPModules = ["net", "web3", "eth", "shh", "personal"] 46 | HTTPVirtualHosts = ["*"] 47 | WSHost = "0.0.0.0" 48 | WSPort = 11331 49 | WSModules = ["net", "web3", "eth", "shh"] 50 | WSOrigins = ["*"] 51 | 52 | [Node.P2P] 53 | MaxPeers = 10 54 | NoDiscovery = false 55 | BootstrapNodes = [] 56 | BootstrapNodesV5 = [] 57 | StaticNodes = [] 58 | TrustedNodes = [] 59 | ListenAddr = ":11333" 60 | EnableMsgEvents = false 61 | 62 | [Dashboard] 63 | Host = "localhost" 64 | Port = 8080 65 | Refresh = 3000000000 66 | -------------------------------------------------------------------------------- /docker/simnet/ethereum/ethereum.simnet.secondary.conf: -------------------------------------------------------------------------------- 1 | # Note: this config doesn't contain the genesis block. 2 | 3 | [Eth] 4 | NetworkId = 876546875 5 | SyncMode = "fast" 6 | LightPeers = 20 7 | DatabaseCache = 128 8 | GasPrice = 18000000000 9 | EnablePreimageRecording = false 10 | 11 | [Eth.Ethash] 12 | CacheDir = "ethash" 13 | CachesInMem = 2 14 | CachesOnDisk = 3 15 | DatasetDir = "/root/.ethereum/ethash.dagdir" 16 | DatasetsInMem = 1 17 | DatasetsOnDisk = 2 18 | 19 | [Eth.TxPool] 20 | NoLocals = false 21 | Journal = "transactions.rlp" 22 | Rejournal = 3600000000000 23 | PriceLimit = 1 24 | PriceBump = 10 25 | AccountSlots = 16 26 | GlobalSlots = 4096 27 | AccountQueue = 64 28 | GlobalQueue = 1024 29 | Lifetime = 10800000000000 30 | 31 | [Eth.GPO] 32 | Blocks = 10 33 | Percentile = 50 34 | 35 | [Shh] 36 | MaxMessageSize = 1048576 37 | MinimumAcceptedPOW = 2e-01 38 | 39 | [Node] 40 | DataDir = "" 41 | NoUSB = true 42 | IPCPath = "/root/.ethereum/geth.ipc" 43 | HTTPHost = "0.0.0.0" 44 | HTTPPort = 11332 45 | HTTPModules = ["net", "web3", "eth", "shh", "personal"] 46 | HTTPVirtualHosts = ["*"] 47 | WSHost = "0.0.0.0" 48 | WSPort = 11331 49 | WSModules = ["net", "web3", "eth", "shh"] 50 | WSOrigins = ["*"] 51 | 52 | [Node.P2P] 53 | MaxPeers = 10 54 | NoDiscovery = false 55 | BootstrapNodes = [] 56 | BootstrapNodesV5 = [] 57 | StaticNodes = [] 58 | TrustedNodes = [] 59 | ListenAddr = ":11333" 60 | EnableMsgEvents = false 61 | 62 | [Dashboard] 63 | Host = "localhost" 64 | Port = 8080 65 | Refresh = 3000000000 66 | -------------------------------------------------------------------------------- /docker/simnet/ethereum/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "chainId": 876546875, 4 | "homesteadBlock": 0, 5 | "eip155Block": 0, 6 | "eip158Block": 0, 7 | "minimumDifficulty": 16384 8 | }, 9 | "difficulty": "0x4000", 10 | "gasLimit": "2100000", 11 | "alloc": {} 12 | } -------------------------------------------------------------------------------- /docker/simnet/litecoin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG LITECOIN_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://download.litecoin.org/litecoin-$LITECOIN_VERSION/linux/litecoin-${LITECOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/litecoind \ 13 | */bin/litecoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # ROLE is bitcoin node role: primary or secondary. 20 | # 21 | # Primary role means that this node will init new blockchain if it not 22 | # exists during deploy or restart. 23 | # 24 | # Secondary rank means that this node will try to connect to primary 25 | # node and use blockchain of latter. 26 | ARG ROLE 27 | 28 | # RPC port. 29 | EXPOSE 12332 30 | 31 | # P2P port. 32 | EXPOSE 12333 33 | 34 | # Copying required binaries from builder stage. 35 | COPY --from=builder litecoind litecoin-cli /usr/local/bin/ 36 | 37 | # Default config used to initalize datadir volume 38 | # at first or cleaned deploy. 39 | COPY litecoin.simnet.$ROLE.conf /root/default/litecoin.conf 40 | 41 | # Entrypoint script used to init datadir if required and for 42 | # starting litecoin daemon. 43 | COPY entrypoint.sh /root/ 44 | 45 | # We are using exec syntax to enable gracefull shutdown. Check 46 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 47 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/simnet/litecoin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.litecoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/litecoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/litecoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | # We are using `exec` to enable gracefull shutdown of running daemon. 30 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 31 | exec litecoind $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/simnet/litecoin/litecoin.simnet.primary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=12333 4 | 5 | rpcbind=0.0.0.0 6 | rpcport=12332 7 | 8 | rpcallowip=0.0.0.0/0 9 | rpcuser=user 10 | rpcpassword=password 11 | 12 | addresstype=legacy 13 | 14 | dnsseed=0 15 | upnp=0 16 | 17 | txindex=1 18 | 19 | printtoconsole=1 -------------------------------------------------------------------------------- /docker/simnet/litecoin/litecoin.simnet.secondary.conf: -------------------------------------------------------------------------------- 1 | regtest=1 2 | 3 | port=12333 4 | 5 | rpcbind=0.0.0.0 6 | rpcport=12332 7 | 8 | rpcallowip=0.0.0.0/0 9 | rpcuser=user 10 | rpcpassword=password 11 | 12 | addresstype=legacy 13 | 14 | dnsseed=0 15 | upnp=0 16 | 17 | txindex=1 18 | 19 | printtoconsole=1 20 | 21 | connect=litecoin.simnet.primary:12333 -------------------------------------------------------------------------------- /docker/simnet/logrotate.conf: -------------------------------------------------------------------------------- 1 | # This is rsyslog configuration for simnet connector dockers' host machine. 2 | 3 | # It intended to be placed at /etc/logrotate.d/connector during deploy 4 | # following with restarting logrotate. 5 | 6 | /var/log/connector/*.log { 7 | # Rotate logs daily. 8 | daily 9 | 10 | # Keep last 7 days. 11 | rotate 7 12 | 13 | # If the log file is missing, go on to the next one without issuing an 14 | # error message. 15 | missingok 16 | 17 | # Do not rotate the log if it is empty. 18 | notifempty 19 | 20 | # Signal rsyslog about rotation to start new log file. 21 | postrotate 22 | invoke-rc.d rsyslog rotate > /dev/null 23 | endscript 24 | } -------------------------------------------------------------------------------- /docker/simnet/rsyslog.conf: -------------------------------------------------------------------------------- 1 | # This is rsyslog configuration for simnet connector dockers' host machine. 2 | 3 | # It intended to be placed at /etc/rsyslog.d/connector.conf during deploy 4 | # following with rsyslogd restarting. 5 | 6 | # Fix `\t` and other caharacters in log messages. 7 | global( 8 | parser.escapecontrolcharactertab="off" 9 | ) 10 | 11 | # Raw log line message template 12 | template(name="outfmt" type="list") { 13 | property(name="msg" position.from="2") 14 | constant(value="\n") 15 | } 16 | 17 | # Template for log dynamic file name to put containers logs in separate 18 | # file in `/var/log/connector/`. E.g. for dash container log file is 19 | # `/var/log/connector/dash.simnet.primary.log`. 20 | template(name="dynaFile" type="list") { 21 | constant(value="/var/log/connector/") 22 | property( 23 | name="syslogtag" 24 | # We parsing syslogtag like `connector-docker/dash.simnet.perimary[123]` 25 | # to get `dash.simnet.perimary` substring. 26 | regex.expression="^connector-docker\\/\\(.\\+\\)\\[" 27 | regex.submatch="1" 28 | ) 29 | constant(value=".log") 30 | } 31 | 32 | # Put connector docker containers' logs in separate files and discard whem. 33 | if ($programname == "connector-docker") then { 34 | action(type="omfile" dynaFile="dynaFile" template="outfmt") 35 | stop 36 | } 37 | -------------------------------------------------------------------------------- /docker/testnet/.env.empty: -------------------------------------------------------------------------------- 1 | # This file contains list of environment variables required to deploy 2 | # testnet connector dockers. You should copy this file to `.env` and 3 | # fill the latter with right values 4 | 5 | # *_AUTH variables should contain authorization string generated by 6 | # `rpcauth.py` script from. You can get it from https://github.com/bitcoin/bitcoin/tree/master/share/rpcauth 7 | # *_USER and *_PASSWORD variables should corresponds to *_AUTH string 8 | 9 | # *_VERSION variables should contain version in N.M.O format. This 10 | # variables are used to construct download link from official sources 11 | # of compiled binaries. So you should be sure that version you specifing 12 | # can be download. Check corresponding `Dockerfile` to get exact download 13 | # link. 14 | 15 | # *_REVISION variables specifies revision or branch which will be used. 16 | # As *_VERSION variables they are used to construct download link of 17 | # source code archive. 18 | # We encourage you to use revision a.k.a commit hash and do not use 19 | # branch name because docker-compose has cache and after first deploy 20 | # downloaded branch is freezed and further deploys will not download 21 | # new branch revisions. In contrast commit specifies exact 22 | # revision of source code so cache is working as should. 23 | 24 | # This three variables intended to be specified outside of .env file. 25 | # You should specify them during deploy on specific droplet. 26 | EXTERNAL_IP= 27 | PRIVATE_IP= 28 | 29 | # `connector` docker container uses `github.com/bitlum/connector`. This 30 | # variable controls which revision (or branch) will be used. 31 | CONNECTOR_REVISION= 32 | 33 | # Force hash is used when connector need to skip syncing of blocks, 34 | # or roll back. 35 | CONNECTOR_BITCOIN_FORCE_HASH= 36 | CONNECTOR_LITECOIN_FORCE_HASH= 37 | CONNECTOR_DASH_FORCE_HASH= 38 | CONNECTOR_BITCOIN_CASH_FORCE_HASH= 39 | CONNECTOR_ETHEREUM_FORCE_HASH= 40 | 41 | BITCOIN_VERSION= 42 | BITCOIN_RPC_AUTH= 43 | BITCOIN_RPC_USER= 44 | BITCOIN_RPC_PASSWORD= 45 | 46 | # `bitcoin-lightning` docker container uses `lnd` from `github.com/bitlum/lnd` 47 | # repo. This variable controls which revision (or branch) will be used. 48 | BITCOIN_LIGHTNING_REVISION= 49 | 50 | # `bitcoin-neutrino` docker container uses btcd from `github.com/btcsuite/btcd` 51 | # repo. This variable controls which revision (or branch) will be used. 52 | BITCOIN_NEUTRION_REVISION= 53 | BITCOIN_NEUTRINO_RPC_USER= 54 | BITCOIN_NEUTRINO_RPC_PASSWORD= 55 | 56 | BITCOIN_CASH_VERSION= 57 | BITCOIN_CASH_RPC_AUTH= 58 | BITCOIN_CASH_RPC_USER= 59 | BITCOIN_CASH_RPC_PASSWORD= 60 | 61 | DASH_VERSION= 62 | DASH_RPC_AUTH= 63 | DASH_RPC_USER= 64 | DASH_RPC_PASSWORD= 65 | 66 | # `ethereum` docker container uses `geth` from `github.com/bitlum/go-ethereum` 67 | # repo. This variable controls which revision (or branch) will be used. 68 | ETHEREUM_REVISION= 69 | # This password will be used to create new ethereum account and to unlock 70 | # existing ethereum accounts. You should generate strong and long password. 71 | ETHEREUM_ACCOUNT_PASSWORD= 72 | 73 | LITECOIN_VERSION= 74 | LITECOIN_RPC_AUTH= 75 | LITECOIN_RPC_USER= 76 | LITECOIN_RPC_PASSWORD= -------------------------------------------------------------------------------- /docker/testnet/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /docker/testnet/bitcoin-cash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_CASH_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://download.bitcoinabc.org/$BITCOIN_CASH_VERSION/linux/bitcoin-abc-${BITCOIN_CASH_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/bitcoind \ 13 | */bin/bitcoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 9332 21 | 22 | # P2P port. 23 | EXPOSE 9333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder bitcoind /usr/local/bin/bitcoin-cashd 27 | COPY --from=builder bitcoin-cli /usr/local/bin/bitcoin-cash-cli 28 | 29 | # Default config used to initalize datadir volume at first or 30 | # cleaned deploy. It will be restored and used after each restart. 31 | COPY bitcoin-cash.testnet.conf /root/default/bitcoin.conf 32 | 33 | # Entrypoint script used to init datadir if required and for 34 | # starting bitcoin cash daemon. 35 | COPY entrypoint.sh /root/ 36 | 37 | # We are using exec syntax to enable gracefull shutdown. Check 38 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 39 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/bitcoin-cash/bitcoin-cash.testnet.conf: -------------------------------------------------------------------------------- 1 | testnet=1 2 | 3 | port=9333 4 | 5 | rpcbind=0.0.0.0 6 | rpcport=9332 7 | 8 | rpcallowip=172.100.1.100 9 | 10 | txindex=1 11 | usecashaddr=0 12 | 13 | printtoconsole=1 14 | 15 | discover=1 16 | listen=1 17 | server=1 -------------------------------------------------------------------------------- /docker/testnet/bitcoin-cash/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.bitcoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/bitcoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/bitcoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec bitcoin-cashd $EXTERNAL_IP_OPT \ 33 | --rpcauth=$BITCOIN_CASH_RPC_AUTH -------------------------------------------------------------------------------- /docker/testnet/bitcoin-lightning/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11-alpine as builder 2 | 3 | ARG BITCOIN_LIGHTNING_REVISION 4 | 5 | # Install dependencies and install/build lnd. 6 | RUN apk add --no-cache --update alpine-sdk \ 7 | git \ 8 | make 9 | 10 | WORKDIR $GOPATH/src/github.com/lightningnetwork/lnd 11 | 12 | # Copy from repository to build from. 13 | RUN git clone https://github.com/lightningnetwork/lnd.git /go/src/github.com/lightningnetwork/lnd 14 | 15 | # Force Go to use the cgo based DNS resolver. This is required to ensure DNS 16 | # queries required to connect to linked containers succeed. 17 | ENV GODEBUG netdns=cgo 18 | 19 | RUN cd /go/src/github.com/lightningnetwork/lnd \ 20 | && git checkout $BITCOIN_LIGHTNING_REVISION \ 21 | && make build-itest \ 22 | && mv lnd-itest /go/bin/lnd \ 23 | && mv lncli-itest /go/bin/lncli 24 | 25 | # Install delve - debugger for the Go programming language, in order to be 26 | # able to attach to the lnd process and understand why it stuck. 27 | RUN go get -u github.com/derekparker/delve/cmd/dlv 28 | 29 | # Start a new, final image to reduce size. 30 | FROM alpine as final 31 | 32 | # Expose lnd ports (server, rpc). 33 | EXPOSE 9735 10009 34 | 35 | # Add bash. 36 | RUN apk add --no-cache \ 37 | bash 38 | 39 | # Copying required binaries from builder stage. 40 | COPY --from=builder /go/bin/lnd /usr/local/bin/ 41 | COPY --from=builder /go/bin/lncli /usr/local/bin/ 42 | COPY --from=builder /go/bin/dlv /usr/local/bin/ 43 | 44 | # Default config used to initalize datadir volume at first or 45 | # cleaned deploy. It will be restored and used after each restart. 46 | COPY bitcoin-lightning.testnet.conf /root/default/lnd.conf 47 | 48 | # Entrypoint script used to init datadir if required and for 49 | # starting bitcoin daemon. 50 | COPY entrypoint.sh /root/ 51 | 52 | # We are using exec syntax to enable gracefull shutdown. Check 53 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 54 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/bitcoin-lightning/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make lnd data persistent. 4 | DATA_DIR=/root/.lnd 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/lnd.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [ ! -d $DATA_DIR ]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp $DEFAULTS_DIR/lnd.conf $CONFIG 22 | 23 | # If external IP defined we need to set corresponding run option 24 | if [ ! -z "$EXTERNAL_IP" ]; then 25 | echo "Setting external IP" 26 | EXTERNAL_IP_OPT="--externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | RPC_USER_OPT="--bitcoind.rpcuser=" 30 | 31 | # We are using `exec` to enable gracefull shutdown of running daemon. 32 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 33 | exec lnd $EXTERNAL_IP_OPT \ 34 | --bitcoind.rpcuser=$BITCOIN_RPC_USER \ 35 | --bitcoind.rpcpass=$BITCOIN_RPC_PASSWORD -------------------------------------------------------------------------------- /docker/testnet/bitcoin-neutrino/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG BITCOIN_NEUTRINO_REVISION 4 | 5 | RUN go get -u github.com/Masterminds/glide 6 | 7 | WORKDIR $GOPATH/src/github.com/btcsuite/btcd 8 | 9 | RUN curl -L https://github.com/btcsuite/btcd/archive/$BITCOIN_NEUTRINO_REVISION.tar.gz \ 10 | | tar xz --strip 1 11 | 12 | RUN glide install 13 | 14 | RUN go install -v . ./cmd/btcctl 15 | 16 | 17 | 18 | FROM ubuntu:18.04 19 | 20 | # P2P port. 21 | EXPOSE 18333 22 | 23 | # Copying required binaries from builder stage. 24 | COPY --from=builder /go/bin/btcd /go/bin/btcctl /usr/local/bin/ 25 | 26 | # Default config used to initalize datadir volume at first or 27 | # cleaned deploy. It will be restored and used after each restart. 28 | COPY bitcoin-neutrino.testnet.conf /root/default/btcd.conf 29 | 30 | # Entrypoint script used to init datadir if required and for 31 | # starting bitcoin daemon. 32 | COPY entrypoint.sh /root/ 33 | 34 | # We are using exec syntax to enable gracefull shutdown. Check 35 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 36 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/bitcoin-neutrino/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make lnd data persistent. 4 | DATA_DIR=/root/.btcd 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/btcd.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [ ! -d $DATA_DIR ]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp $DEFAULTS_DIR/btcd.conf $CONFIG 22 | 23 | # If external IP defined we need to set corresponding run option 24 | if [ ! -z "$EXTERNAL_IP" ]; then 25 | echo "Setting external IP" 26 | EXTERNAL_IP_OPT="--externalip=$EXTERNAL_IP" 27 | fi 28 | 29 | RPC_USER_OPT="--bitcoind.rpcuser=" 30 | 31 | # We are using `exec` to enable gracefull shutdown of running daemon. 32 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 33 | exec btcd $EXTERNAL_IP_OPT \ 34 | --rpcuser=$BITCOIN_NEUTRINO_RPC_USER 35 | --rpcpass=$BITCOIN_NEUTRINO_RPC_PASSWORD -------------------------------------------------------------------------------- /docker/testnet/bitcoin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG BITCOIN_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://bitcoin.org/bin/bitcoin-core-$BITCOIN_VERSION/bitcoin-${BITCOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/bitcoind \ 13 | */bin/bitcoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 13332 21 | 22 | # P2P port. 23 | EXPOSE 13333 24 | 25 | # `zmqpubrawblock` and `zmqpubrawtx` port. 26 | EXPOSE 8334 27 | 28 | # Copying required binaries from builder stage. 29 | COPY --from=builder bitcoind bitcoin-cli /usr/local/bin/ 30 | 31 | # Default config used to initalize datadir volume at first or 32 | # cleaned deploy. It will be restored and used after each restart. 33 | COPY bitcoin.testnet.conf /root/default/bitcoin.conf 34 | 35 | # Entrypoint script used to init datadir if required and for 36 | # starting bitcoin daemon. 37 | COPY entrypoint.sh /root/ 38 | 39 | # We are using exec syntax to enable gracefull shutdown. Check 40 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 41 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/bitcoin/bitcoin.testnet.conf: -------------------------------------------------------------------------------- 1 | testnet=1 2 | discover=1 3 | listen=1 4 | server=1 5 | txindex=1 6 | printtoconsole=1 7 | 8 | 9 | [test] 10 | port=8333 11 | rpcbind=0.0.0.0 12 | rpcport=8332 13 | rpcallowip=0.0.0.0/0 14 | 15 | # Increasing rcpthreads to workaround possible bug described here 16 | # https://github.com/lightningnetwork/lnd/issues/1174. 17 | rpcthreads=16 18 | 19 | zmqpubrawblock=tcp://0.0.0.0:8334 20 | zmqpubrawtx=tcp://0.0.0.0:8335 21 | 22 | # getaddressesbyaccount is deprecated and will be removed in V0.18. 23 | # but we need it right now. 24 | deprecatedrpc=accounts 25 | deprecatedrpc=signrawtransaction -------------------------------------------------------------------------------- /docker/testnet/bitcoin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.bitcoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/bitcoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/bitcoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec bitcoind $EXTERNAL_IP_OPT \ 33 | --rpcauth=$BITCOIN_RPC_AUTH -------------------------------------------------------------------------------- /docker/testnet/connector/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12 AS builder 2 | 3 | WORKDIR $GOPATH/src/github.com/bitlum/connector/ 4 | 5 | ARG CONNECTOR_REVISION 6 | 7 | RUN curl -L https://github.com/bitlum/connector/archive/$CONNECTOR_REVISION.tar.gz \ 8 | | tar xz --strip 1 9 | 10 | RUN GO111MODULE=on go get 11 | RUN GO111MODULE=on go install . ./cmd/... 12 | 13 | FROM ubuntu:18.04 14 | 15 | RUN apt-get update && apt-get install -y \ 16 | ca-certificates \ 17 | curl \ 18 | && rm -rf /var/lib/apt/lists/* 19 | 20 | # Copying required binaries from builder stage. 21 | COPY --from=builder /go/bin/connector /usr/local/bin 22 | COPY --from=builder /go/bin/pscli /usr/local/bin 23 | 24 | # Default config used to initalize datadir volume at first or 25 | # cleaned deploy. It will be restored and used after each restart. 26 | COPY connector.testnet.conf /root/default/connector.conf 27 | 28 | # Entrypoint script used to init datadir if required and for 29 | # starting dash daemon 30 | COPY entrypoint.sh /root/ 31 | 32 | # We are using exec syntax to enable gracefull shutdown. Check 33 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 34 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] 35 | -------------------------------------------------------------------------------- /docker/testnet/connector/connector.testnet.conf: -------------------------------------------------------------------------------- 1 | [Application Options] 2 | datadir=/root/.connector 3 | debuglevel=trace 4 | network=testnet 5 | 6 | # RPC address to bind 7 | rpchost=0.0.0.0 8 | rpcport=9002 9 | 10 | [Bitcoin] 11 | bitcoin.disable=false 12 | bitcoin.minconfirmations=1 13 | bitcoin.syncdelay=5 14 | bitcoin.host=bitcoin.testnet 15 | bitcoin.port=8332 16 | bitcoin.feeperunit=350 17 | 18 | [Bitcoincash] 19 | bitcoincash.disable=false 20 | bitcoincash.minconfirmations=1 21 | bitcoincash.syncdelay=5 22 | bitcoincash.host=bitcoin-cash.testnet 23 | bitcoincash.port=9332 24 | bitcoincash.feeperunit=350 25 | 26 | [Dash] 27 | dash.disable=false 28 | dash.minconfirmations=1 29 | dash.syncdelay=5 30 | dash.host=dash.testnet 31 | dash.port=10332 32 | dash.feeperunit=350 33 | 34 | [Ethereum] 35 | ethereum.disable=false 36 | ethereum.minconfirmations=1 37 | ethereum.syncdelay=5 38 | ethereum.host=ethereum.testnet 39 | ethereum.port=11332 40 | 41 | [Litecoin] 42 | litecoin.disable=false 43 | litecoin.minconfirmations=1 44 | litecoin.syncdelay=5 45 | litecoin.host=litecoin.testnet 46 | litecoin.port=12332 47 | litecoin.feeperunit=350 48 | 49 | [Bitcoinlightning] 50 | bitcoinlightning.disable=false 51 | bitcoinlightning.tlscertpath=/root/.lnd/tls.cert 52 | bitcoinlightning.macaroonpath=/root/.lnd/data/chain/bitcoin/testnet/admin.macaroon 53 | # lnd RPC address 54 | bitcoinlightning.host=bitcoin-lightning.testnet 55 | bitcoinlightning.port=10009 56 | # lnd P2P address 57 | bitcoinlightning.peerhost=testnet.connector.bitlum.io 58 | bitcoinlightning.peerport=9735 -------------------------------------------------------------------------------- /docker/testnet/connector/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make connector data persistent. 4 | DATA_DIR=/root/.connector 5 | 6 | # This path is expected to have default data used to init environment 7 | # at first deploy such as config. 8 | DEFAULTS_DIR=/root/default 9 | 10 | CONFIG=$DATA_DIR/connector.conf 11 | 12 | # If data directory doesn't exists this means that volume is not mounted 13 | # or mounted incorrectly, so we must fail. 14 | if [[ ! -d ${DATA_DIR} ]]; then 15 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 16 | exit 1 17 | fi 18 | 19 | # We always restoring default config shipped with docker. 20 | echo "Restoring default config" 21 | cp ${DEFAULTS_DIR}/connector.conf ${CONFIG} 22 | 23 | # We are using `exec` to enable gracefull shutdown of running daemon. 24 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 25 | exec connector --config /root/.connector/connector.conf \ 26 | --bitcoin.user=${BITCOIN_RPC_USER} \ 27 | --bitcoin.password=${BITCOIN_RPC_PASSWORD} \ 28 | --bitcoin.forcelasthash=${CONNECTOR_BITCOIN_FORCE_HASH} \ 29 | --bitcoincash.user=${BITCOIN_CASH_RPC_USER} \ 30 | --bitcoincash.password=${BITCOIN_CASH_RPC_PASSWORD} \ 31 | --bitcoincash.forcelasthash=${CONNECTOR_BITCOIN_CASH_FORCE_HASH} \ 32 | --dash.user=${DASH_RPC_USER} \ 33 | --dash.password=${DASH_RPC_PASSWORD} \ 34 | --dash.forcelasthash=${CONNECTOR_DASH_FORCE_HASH} \ 35 | --ethereum.password=${ETHEREUM_ACCOUNT_PASSWORD} \ 36 | --ethereum.forcelasthash=${CONNECTOR_ETHEREUM_FORCE_HASH} 37 | --litecoin.user=${LITECOIN_RPC_USER} \ 38 | --litecoin.password=${LITECOIN_RPC_PASSWORD} \ 39 | --litecoin.forcelasthash=${CONNECTOR_LITECOIN_FORCE_HASH} 40 | -------------------------------------------------------------------------------- /docker/testnet/dash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG DASH_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl -L https://github.com/dashpay/dash/releases/download/v$DASH_VERSION/dashcore-${DASH_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/dashd \ 13 | */bin/dash-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 10332 21 | 22 | # P2P port. 23 | EXPOSE 10333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder dashd dash-cli /usr/local/bin/ 27 | 28 | # Default config used to initalize datadir volume at first or 29 | # cleaned deploy. It will be restored and used after each restart. 30 | COPY dash.testnet.conf /root/default/dash.conf 31 | 32 | # Entrypoint script used to init datadir if required and for 33 | # starting dash daemon. 34 | COPY entrypoint.sh /root/ 35 | 36 | # We are using exec syntax to enable gracefull shutdown. Check 37 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 38 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/dash/dash.testnet.conf: -------------------------------------------------------------------------------- 1 | testnet=1 2 | 3 | port=10333 4 | rpcport=10332 5 | 6 | rpcallowip=172.100.1.100 7 | 8 | txindex=1 9 | 10 | printtoconsole=1 11 | 12 | listen=1 13 | server=1 -------------------------------------------------------------------------------- /docker/testnet/dash/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.dashcore 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/dash.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/dash.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec dashd $EXTERNAL_IP_OPT \ 33 | --rpcauth=$DASH_RPC_AUTH -------------------------------------------------------------------------------- /docker/testnet/ethereum/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.10.3 AS builder 2 | 3 | ARG ETHEREUM_REVISION 4 | 5 | WORKDIR /ethereum 6 | 7 | RUN curl -L https://github.com/bitlum/go-ethereum/archive/$ETHEREUM_REVISION.tar.gz \ 8 | | tar xz --strip 1 9 | 10 | RUN make geth 11 | 12 | 13 | 14 | FROM ubuntu:18.04 15 | 16 | # RPC port. 17 | EXPOSE 11332 18 | 19 | # RPC-WS port. 20 | EXPOSE 11331 21 | 22 | # P2P port. 23 | EXPOSE 11333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder /ethereum/build/bin/geth /usr/local/bin/ 27 | 28 | # Default config and genesis used to initalize datadir volume 29 | # at first or cleaned deploy. 30 | COPY ethereum.testnet.conf /root/default/ethereum.conf 31 | 32 | # Entrypoint script used to init datadir if required and for 33 | # starting bitcoin daemon. 34 | COPY entrypoint.sh /root/ 35 | 36 | # We are using exec syntax to enable gracefull shutdown. Check 37 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 38 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/ethereum/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This directory is expected to be volume to make blockchain and account 4 | # data persistent. 5 | DATA_DIR=/root/.ethereum 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/ethereum.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/ethereum.conf $CONFIG 23 | 24 | # At first deploy datadir keystore path should not exists which means 25 | # that account is not created, so we are creating it. 26 | KEYSTORE=$DATA_DIR/keystore 27 | if [ ! -d $KEYSTORE ] || [ ! "$(ls -A $KEYSTORE)" ]; then 28 | echo "Creating new account" 29 | geth --datadir $DATA_DIR --config $CONFIG account new --password /dev/null 30 | fi 31 | 32 | # If external IP defined when we need to set corresponding run option 33 | if [ ! -z "$EXTERNAL_IP" ]; then 34 | echo "Setting external IP" 35 | EXTERNAL_IP_OPT="--nat extip:$EXTERNAL_IP" 36 | fi 37 | 38 | # We are using `exec` to enable gracefull shutdown of running daemon. 39 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 40 | exec geth \ 41 | --datadir $DATA_DIR \ 42 | --config $CONFIG \ 43 | $EXTERNAL_IP_OPT -------------------------------------------------------------------------------- /docker/testnet/ethereum/ethereum.testnet.conf: -------------------------------------------------------------------------------- 1 | [Eth] 2 | NetworkId = 4 3 | SyncMode = "fast" 4 | NoPruning = false 5 | LightPeers = 100 6 | DatabaseCache = 512 7 | TrieCleanCache = 256 8 | TrieDirtyCache = 256 9 | TrieTimeout = 3600000000000 10 | MinerGasFloor = 8000000 11 | MinerGasCeil = 8000000 12 | MinerGasPrice = 1000000000 13 | MinerRecommit = 3000000000 14 | MinerNoverify = false 15 | EnablePreimageRecording = false 16 | EWASMInterpreter = "" 17 | EVMInterpreter = "" 18 | 19 | [Eth.Ethash] 20 | CacheDir = "ethash" 21 | CachesInMem = 2 22 | CachesOnDisk = 3 23 | DatasetDir = "/root/.ethereum/ethash.dagdir" 24 | DatasetsInMem = 1 25 | DatasetsOnDisk = 2 26 | PowMode = 0 27 | 28 | [Eth.TxPool] 29 | Locals = [] 30 | NoLocals = false 31 | Journal = "transactions.rlp" 32 | Rejournal = 3600000000000 33 | PriceLimit = 1 34 | PriceBump = 10 35 | AccountSlots = 16 36 | GlobalSlots = 4096 37 | AccountQueue = 64 38 | GlobalQueue = 1024 39 | Lifetime = 10800000000000 40 | 41 | [Eth.GPO] 42 | Blocks = 20 43 | Percentile = 60 44 | 45 | [Shh] 46 | MaxMessageSize = 1048576 47 | MinimumAcceptedPOW = 2e-01 48 | RestrictConnectionBetweenLightClients = true 49 | 50 | [Node] 51 | DataDir = "" 52 | NoUSB = true 53 | IPCPath = "/root/.ethereum/geth.ipc" 54 | HTTPHost = "0.0.0.0" 55 | HTTPPort = 11332 56 | HTTPModules = ["net", "web3", "eth", "shh", "personal"] 57 | HTTPVirtualHosts = ["ethereum.testnet"] 58 | WSHost = "0.0.0.0" 59 | WSPort = 11331 60 | WSModules = ["net", "web3", "eth", "shh"] 61 | WSOrigins = ["ethereum.testnet"] 62 | 63 | [Node.P2P] 64 | MaxPeers = 25 65 | NoDiscovery = false 66 | BootstrapNodes = ["enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://3f1d12044546b76342d59d4a05532c14b85aa669704bfe1f864fe079415aa2c02d743e03218e57a33fb94523adb54032871a6c51b2cc5514cb7c7e35b3ed0a99@13.93.211.84:30303", "enode://78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d@191.235.84.50:30303", "enode://158f8aab45f6d19c6cbf4a089c2670541a8da11978a2f90dbf6a502a4a3bab80d288afdbeb7ec0ef6d92de563767f3b1ea9e8e334ca711e9f8e2df5a0385e8e6@13.75.154.138:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", "enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"] 67 | BootstrapNodesV5 = ["enode://06051a5573c81934c9554ef2898eb13b33a34b94cf36b202b69fde139ca17a85051979867720d4bdae4323d4943ddf9aeeb6643633aa656e0be843659795007a@35.177.226.168:30303", "enode://0cc5f5ffb5d9098c8b8c62325f3797f56509bff942704687b6530992ac706e2cb946b90a34f1f19548cd3c7baccbcaea354531e5983c7d1bc0dee16ce4b6440b@40.118.3.223:30304", "enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30306", "enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30307"] 68 | StaticNodes = [] 69 | TrustedNodes = [] 70 | ListenAddr = ":11333" 71 | EnableMsgEvents = false 72 | 73 | [Node.HTTPTimeouts] 74 | ReadTimeout = 30000000000 75 | WriteTimeout = 30000000000 76 | IdleTimeout = 120000000000 -------------------------------------------------------------------------------- /docker/testnet/litecoin/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 AS builder 2 | 3 | ARG LITECOIN_VERSION 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | ca-certificates \ 7 | curl \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | RUN curl https://download.litecoin.org/litecoin-$LITECOIN_VERSION/linux/litecoin-${LITECOIN_VERSION}-x86_64-linux-gnu.tar.gz \ 11 | | tar xz --wildcards --strip 2 \ 12 | */bin/litecoind \ 13 | */bin/litecoin-cli 14 | 15 | 16 | 17 | FROM ubuntu:18.04 18 | 19 | # RPC port. 20 | EXPOSE 12332 21 | 22 | # P2P port. 23 | EXPOSE 12333 24 | 25 | # Copying required binaries from builder stage. 26 | COPY --from=builder litecoind litecoin-cli /usr/local/bin/ 27 | 28 | # Default config used to initalize datadir volume 29 | # at first or cleaned deploy. 30 | COPY litecoin.testnet.conf /root/default/litecoin.conf 31 | 32 | # Entrypoint script used to init datadir if required and for 33 | # starting litecoin daemon. 34 | COPY entrypoint.sh /root/ 35 | 36 | # We are using exec syntax to enable gracefull shutdown. Check 37 | # http://veithen.github.io/2014/11/16/sigterm-propagation.html. 38 | ENTRYPOINT ["bash", "/root/entrypoint.sh"] -------------------------------------------------------------------------------- /docker/testnet/litecoin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This path is expected to be volume to make blockchain and accounts 4 | # data persistent. 5 | DATA_DIR=/root/.litecoin 6 | 7 | # This path is expected to have default data used to init environment 8 | # at first deploy such as config and genesis. 9 | DEFAULTS_DIR=/root/default 10 | 11 | CONFIG=$DATA_DIR/litecoin.conf 12 | 13 | # If data directory doesn't exists this means that volume is not mounted 14 | # or mounted incorrectly, so we must fail. 15 | if [ ! -d $DATA_DIR ]; then 16 | echo "Data directory '$DATA_DIR' doesn't exists. Check your volume configuration." 17 | exit 1 18 | fi 19 | 20 | # We always restoring default config shipped with docker. 21 | echo "Restoring default config" 22 | cp $DEFAULTS_DIR/litecoin.conf $CONFIG 23 | 24 | # If external IP defined when we need to set corresponding run option 25 | if [ ! -z "$EXTERNAL_IP" ]; then 26 | echo "Setting external IP" 27 | EXTERNAL_IP_OPT="-externalip=$EXTERNAL_IP" 28 | fi 29 | 30 | # We are using `exec` to enable gracefull shutdown of running daemon. 31 | # Check http://veithen.github.io/2014/11/16/sigterm-propagation.html. 32 | exec litecoind $EXTERNAL_IP_OPT \ 33 | --rpcauth=$LITECOIN_RPC_AUTH -------------------------------------------------------------------------------- /docker/testnet/litecoin/litecoin.testnet.conf: -------------------------------------------------------------------------------- 1 | testnet=1 2 | 3 | port=12333 4 | 5 | rpcbind=0.0.0.0 6 | rpcport=12332 7 | 8 | rpcallowip=172.100.1.100 9 | 10 | addresstype=legacy 11 | 12 | txindex=1 13 | 14 | printtoconsole=1 15 | 16 | discover=1 17 | listen=1 18 | server=1 -------------------------------------------------------------------------------- /docker/testnet/logrotate.conf: -------------------------------------------------------------------------------- 1 | # This is rsyslog configuration for testnet connector dockers' host machine. 2 | 3 | # It intended to be placed at /etc/logrotate.d/connector during deploy 4 | # following with restarting logrotate. 5 | 6 | /var/log/connector/*.log { 7 | # Rotate logs daily. 8 | daily 9 | 10 | # Keep last 30 days. 11 | rotate 30 12 | 13 | # If the log file is missing, go on to the next one without issuing an 14 | # error message. 15 | missingok 16 | 17 | # Do not rotate the log if it is empty. 18 | notifempty 19 | 20 | # Postpone compression of the previous log file to the next rotation cycle. 21 | delaycompress 22 | 23 | # Old versions of log files are compressed with gzip(1). 24 | compress 25 | 26 | # Signal rsyslog about rotation to start new log file. 27 | postrotate 28 | invoke-rc.d rsyslog rotate > /dev/null 29 | endscript 30 | } -------------------------------------------------------------------------------- /docker/testnet/rsyslog.conf: -------------------------------------------------------------------------------- 1 | # This is rsyslog configuration for testnet connector dockers' host machine. 2 | 3 | # It intended to be placed at /etc/rsyslog.d/connector.conf during deploy 4 | # following with rsyslogd restarting. 5 | 6 | # Fix `\t` and other caharacters in log messages. 7 | global( 8 | parser.escapecontrolcharactertab="off" 9 | ) 10 | 11 | # Raw log line message template 12 | template(name="outfmt" type="list") { 13 | property(name="msg" position.from="2") 14 | constant(value="\n") 15 | } 16 | 17 | # Template for log dynamic file name to put containers logs in separate 18 | # file in `/var/log/connector/`. E.g. for dash container log file is 19 | # `/var/log/connector/dash.simnet.primary.log`. 20 | template(name="dynaFile" type="list") { 21 | constant(value="/var/log/connector/") 22 | property( 23 | name="syslogtag" 24 | # We parsing syslogtag like `connector-docker/dash.simnet.perimary[123]` 25 | # to get `dash.simnet.perimary` substring. 26 | regex.expression="^connector-docker\\/\\(.\\+\\)\\[" 27 | regex.submatch="1" 28 | ) 29 | constant(value=".log") 30 | } 31 | 32 | # Put connector docker containers' logs in separate files and discard whem. 33 | if ($programname == "connector-docker") then { 34 | action(type="omfile" dynaFile="dynaFile" template="outfmt") 35 | stop 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bitlum/connector 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/bitlum/btcd v0.0.0-20180201152249-072770c03d1d 7 | github.com/bitlum/btcutil v0.0.0-20171119084920-ff69c1bdcd79 // indirect 8 | github.com/bitlum/go-bitcoind-rpc v0.0.0-20181122191953-5503508ef045 9 | github.com/bitlum/graphql-go v0.0.0-20171223131140-e232a94bee37 10 | github.com/boltdb/bolt v1.3.1 // indirect 11 | github.com/btcsuite/btcd v0.0.0-20190315201642-aa6e0f35703c 12 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f 13 | github.com/btcsuite/btcutil v0.0.0-20190316010144-3ac1210f4b38 14 | github.com/btcsuite/btcwallet v0.0.0-20190319010515-89ab2044f962 15 | github.com/btcsuite/go-flags v0.0.0-20150116065318-6c288d648c1c 16 | github.com/davecgh/go-spew v1.1.1 17 | github.com/ethereum/go-ethereum v1.8.23 18 | github.com/go-errors/errors v1.0.1 19 | github.com/golang/protobuf v1.3.1 20 | github.com/jarcoal/httpmock v1.0.0 // indirect 21 | github.com/jinzhu/gorm v1.9.2 22 | github.com/jrick/logrotate v1.0.0 23 | github.com/lightningnetwork/lnd v0.5.1-beta.0.20190322040823-c7ca387a9d92 24 | github.com/ltcsuite/ltcd v0.0.0-20190215003858-73a737535028 25 | github.com/mr-tron/base58 v1.1.1 // indirect 26 | github.com/onrik/ethrpc v0.0.0-20190305112807-6b8e9c0e9a8f 27 | github.com/pkg/errors v0.8.1 28 | github.com/prometheus/client_golang v0.9.2 29 | github.com/schancel/cashaddr-converter v0.0.0-20181111022653-4769e7add95a 30 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 31 | github.com/tidwall/gjson v1.2.1 // indirect 32 | github.com/tidwall/match v1.0.1 // indirect 33 | github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 // indirect 34 | github.com/urfave/cli v1.18.0 35 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 36 | google.golang.org/grpc v1.19.1 37 | gopkg.in/gormigrate.v1 v1.4.0 38 | gopkg.in/macaroon.v2 v2.1.0 39 | ) 40 | -------------------------------------------------------------------------------- /metrics/labels.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | // Severity defines how severe the problem we faced, depending on this we 4 | // farther react on the problem differently. For example in Prometheus 5 | // metric backend implementation we setup the alert rules on the server 6 | // which notifies as about the problem. 7 | type Severity string 8 | 9 | var ( 10 | // HighSeverity high level of error importance. 11 | HighSeverity Severity = "high" 12 | 13 | // MiddleSeverity middle level of err importance. 14 | MiddleSeverity Severity = "middle" 15 | 16 | // LowSeverity low level of error importance. 17 | LowSeverity Severity = "low" 18 | ) 19 | -------------------------------------------------------------------------------- /metrics/log.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/btcsuite/btclog" 5 | ) 6 | 7 | // log is a logger that is initialized with no output filters. This 8 | // means the package will not perform any logging by default until the caller 9 | // requests it. 10 | var log btclog.Logger 11 | 12 | // The default amount of logging is none. 13 | func init() { 14 | DisableLog() 15 | } 16 | 17 | // DisableLog disables all library log output. Logging output is disabled 18 | // by default until UseLogger is called. 19 | func DisableLog() { 20 | log = btclog.Disabled 21 | } 22 | 23 | // UseLogger uses a specified Logger to output package logging info. 24 | // This should be used in preference to SetLogWriter if the caller is also 25 | // using btclog. 26 | func UseLogger(logger btclog.Logger) { 27 | log = logger 28 | } 29 | 30 | // logClosure is used to provide a closure over expensive logging operations 31 | // so don't have to be performed when the logging level doesn't warrant it. 32 | type logClosure func() string 33 | 34 | // String invokes the underlying function and returns the result. 35 | func (c logClosure) String() string { 36 | return c() 37 | } 38 | 39 | // newLogClosure returns a new closure over a function that returns a string 40 | // which itself provides a Stringer interface so that it can be used with the 41 | // logging system. 42 | func newLogClosure(c func() string) logClosure { 43 | return logClosure(c) 44 | } 45 | -------------------------------------------------------------------------------- /metrics/rpc/backend.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/bitlum/graphql-go/errors" 6 | "github.com/bitlum/connector/metrics" 7 | ) 8 | 9 | const ( 10 | // subsystem is used as the second part in the name of the metric, 11 | // after the metrics namespace. 12 | subsystem = "rpc" 13 | 14 | // requestLabel is used to distinguish different RPC requests during the 15 | // process of metric analysis and alert rule constructing. 16 | requestLabel = "request" 17 | 18 | // severityLabel is used to distinguish different error codes by its 19 | // level of importance. 20 | severityLabel = "severity" 21 | ) 22 | 23 | // MetricsBackend is a system which is responsible for receiving and storing 24 | // the connector metrics. 25 | type MetricsBackend interface { 26 | AddError(request, severity string) 27 | } 28 | 29 | // EmptyBackend is used as an empty metrics backend in order to avoid 30 | type EmptyBackend struct{} 31 | 32 | func (b *EmptyBackend) AddError(request, severity string) {} 33 | 34 | // PrometheusBackend is the main subsystem metrics implementation. Uses 35 | // prometheus metrics singletons defined above. 36 | // 37 | // WARN: Method name should be taken from limited set. 38 | // Don't use dynamic naming, it may cause dramatic increase of the amount of 39 | // data on the metric server. 40 | // 41 | // NOTE: Non-pointer receiver made by intent to avoid conflict in the system 42 | // with parallel metrics report. 43 | type PrometheusBackend struct { 44 | errorsTotal *prometheus.CounterVec 45 | } 46 | 47 | // AddError increases error counter for the given method name. 48 | // 49 | // WARN: Error code name should be taken from limited set. 50 | // Don't use dynamic naming, it may cause dramatic increase of the amount of 51 | // data on the metric server. 52 | // 53 | // NOTE: Non-pointer receiver made by intent to avoid conflict in the system 54 | // with parallel metrics report. 55 | func (m PrometheusBackend) AddError(method, severity string) { 56 | m.errorsTotal.With( 57 | prometheus.Labels{ 58 | requestLabel: method, 59 | severityLabel: severity, 60 | }, 61 | ).Add(1) 62 | } 63 | 64 | // InitMetricsBackend creates subsystem metrics for specified 65 | // net. Creates and tries to register metrics singletons. If register was 66 | // already done, than function not returning error. 67 | func InitMetricsBackend(net string) (MetricsBackend, error) { 68 | backend := PrometheusBackend{} 69 | 70 | backend.errorsTotal = prometheus.NewCounterVec( 71 | prometheus.CounterOpts{ 72 | Namespace: metrics.Namespace, 73 | Subsystem: subsystem, 74 | Name: "errors_total", 75 | Help: "Total requests which processing ended with error", 76 | ConstLabels: prometheus.Labels{ 77 | metrics.NetLabel: net, 78 | }, 79 | }, 80 | []string{ 81 | requestLabel, 82 | severityLabel, 83 | }, 84 | ) 85 | 86 | if err := prometheus.Register(backend.errorsTotal); err != nil { 87 | // Skip returning error if we re-registered metric. 88 | if _, ok := err.(prometheus.AlreadyRegisteredError); !ok { 89 | return backend, errors.Errorf( 90 | "unable to register 'errorsTotal' metric: " + 91 | err.Error()) 92 | } 93 | } 94 | 95 | return backend, nil 96 | } 97 | -------------------------------------------------------------------------------- /metrics/server.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/prometheus/client_golang/prometheus/promhttp" 7 | "time" 8 | ) 9 | 10 | const ( 11 | // Namespace is used to distinguish this metrics from other. Namespace is 12 | // global for the project, for that reason ensure that name not interfere 13 | // with other namespaces on your prometheus server. 14 | Namespace = "connector" 15 | 16 | // NetLabel is used to distinguish different blockchain network names in 17 | // which service is working e.g simnet, testnet, mainnet, during the 18 | // process of metric analysis and alert rule constructing. 19 | NetLabel = "net" 20 | ) 21 | 22 | func StartServer(addr string) *http.Server { 23 | 24 | handler := http.NewServeMux() 25 | handler.Handle("/metrics", promhttp.Handler()) 26 | 27 | server := &http.Server{Addr: addr, Handler: handler} 28 | 29 | go func() { 30 | log.Infof("Starting metrics http server on `%s`", addr) 31 | 32 | for { 33 | switch err := server.ListenAndServe(); err { 34 | case http.ErrServerClosed: 35 | log.Infof("Metrics http server shutdown") 36 | return 37 | default: 38 | log.Errorf("Metrics http server error: %v", err) 39 | 40 | time.Sleep(5 * time.Second) 41 | 42 | log.Infof("Trying to start metrics http"+ 43 | " server on '%s' one more time", addr) 44 | } 45 | } 46 | }() 47 | 48 | return server 49 | } 50 | -------------------------------------------------------------------------------- /signal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | ) 8 | 9 | // Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go 10 | 11 | // interruptChannel is used to receive SIGINT (Ctrl+C) signals. 12 | var interruptChannel chan os.Signal 13 | 14 | // shutdownRequestChannel is used to request the daemon to shutdown gracefully, 15 | // similar to when receiveing SIGINT. 16 | var shutdownRequestChannel = make(chan struct{}) 17 | 18 | // addHandlerChannel is used to add an interrupt handler to the list of handlers 19 | // to be invoked on SIGINT (Ctrl+C) signals and shutdown. 20 | var addHandlerChannel = make(chan func()) 21 | 22 | // mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the 23 | // interruptChannel and shutdown requests on the shutdownRequestChannel, and 24 | // invokes the registered interruptCallbacks accordingly. It also listens for 25 | // callback registration. 26 | // It must be run as a goroutine. 27 | func mainInterruptHandler(shutdownChannel chan struct{}) { 28 | // interruptCallbacks is a list of callbacks to invoke when a 29 | // SIGINT (Ctrl+C) or a shutdown request is received. 30 | var interruptCallbacks []func() 31 | 32 | // isShutdown is a flag which is used to indicate whether or not 33 | // the shutdown signal has already been received and hence any future 34 | // attempts to add a new interrupt handler should invoke them 35 | // immediately. 36 | var isShutdown bool 37 | 38 | // shutdown invokes the registered interrupt handlers, then signals the 39 | // shutdownChannel. 40 | shutdown := func() { 41 | // Ignore more than one shutdown signal. 42 | if isShutdown { 43 | log.Println("Already shutting down...") 44 | return 45 | } 46 | isShutdown = true 47 | log.Println("Shutting down...") 48 | 49 | // Run handlers in LIFO order. 50 | for i := range interruptCallbacks { 51 | idx := len(interruptCallbacks) - 1 - i 52 | callback := interruptCallbacks[idx] 53 | callback() 54 | } 55 | 56 | // Signal the main goroutine to shutdown. 57 | go func() { 58 | shutdownChannel <- struct{}{} 59 | }() 60 | } 61 | 62 | for { 63 | select { 64 | case <-interruptChannel: 65 | log.Println("Received SIGINT (Ctrl+C).") 66 | shutdown() 67 | 68 | case <-shutdownRequestChannel: 69 | log.Println("Received shutdown request.") 70 | shutdown() 71 | 72 | case handler := <-addHandlerChannel: 73 | // The shutdown signal has already been received, so 74 | // just invoke any new handlers immediately. 75 | if isShutdown { 76 | handler() 77 | } 78 | 79 | interruptCallbacks = append(interruptCallbacks, handler) 80 | } 81 | } 82 | } 83 | 84 | // addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) or a 85 | // shutdown request is received. 86 | func addInterruptHandler(shutdownChannel chan struct{}, handler func()) { 87 | // Create the channel and start the main interrupt handler which invokes 88 | // all other callbacks and exits if not already done. 89 | if interruptChannel == nil { 90 | interruptChannel = make(chan os.Signal, 1) 91 | signal.Notify(interruptChannel, os.Interrupt) 92 | go mainInterruptHandler(shutdownChannel) 93 | } 94 | 95 | addHandlerChannel <- handler 96 | } 97 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // fileExists reports whether the named file or directory exists. 8 | // This function is taken from https://github.com/btcsuite/btcd 9 | func fileExists(name string) bool { 10 | if _, err := os.Stat(name); err != nil { 11 | if os.IsNotExist(err) { 12 | return false 13 | } 14 | } 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Heavily inspired by https://github.com/btcsuite/btcd/blob/master/version.go 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" 12 | 13 | // These constants define the application version and follow the semantic 14 | // versioning 2.0.0 spec (http://semver.org/). 15 | const ( 16 | appMajor uint = 0 17 | appMinor uint = 1 18 | appPatch uint = 0 19 | 20 | // appPreRelease MUST only contain characters from semanticAlphabet 21 | // per the semantic versioning spec. 22 | appPreRelease = "alpha" 23 | ) 24 | 25 | // appBuild is defined as a variable so it can be overridden during the build 26 | // process with '-ldflags "-X main.appBuild foo' if needed. It MUST only 27 | // contain characters from semanticAlphabet per the semantic versioning spec. 28 | var appBuild string 29 | 30 | // version returns the application version as a properly formed string per the 31 | // semantic versioning 2.0.0 spec (http://semver.org/). 32 | func version() string { 33 | // Start with the major, minor, and patch versions. 34 | version := fmt.Sprintf("%d.%d.%d", appMajor, appMinor, appPatch) 35 | 36 | // Append pre-release version if there is one. The hyphen called for 37 | // by the semantic versioning spec is automatically appended and should 38 | // not be contained in the pre-release string. The pre-release version 39 | // is not appended if it contains invalid characters. 40 | preRelease := normalizeVerString(appPreRelease) 41 | if preRelease != "" { 42 | version = fmt.Sprintf("%s-%s", version, preRelease) 43 | } 44 | 45 | // Append build metadata if there is any. The plus called for 46 | // by the semantic versioning spec is automatically appended and should 47 | // not be contained in the build metadata string. The build metadata 48 | // string is not appended if it contains invalid characters. 49 | build := normalizeVerString(appBuild) 50 | if build != "" { 51 | version = fmt.Sprintf("%s+%s", version, build) 52 | } 53 | 54 | return version 55 | } 56 | 57 | // normalizeVerString returns the passed string stripped of all characters which 58 | // are not valid according to the semantic versioning guidelines for pre-release 59 | // version and build metadata strings. In particular they MUST only contain 60 | // characters in semanticAlphabet. 61 | func normalizeVerString(str string) string { 62 | var result bytes.Buffer 63 | for _, r := range str { 64 | if strings.ContainsRune(semanticAlphabet, r) { 65 | result.WriteRune(r) 66 | } 67 | } 68 | return result.String() 69 | } 70 | --------------------------------------------------------------------------------