diff --git a/chain/bitgesell/address.go b/chain/bitgesell/address.go new file mode 100644 index 00000000..a42e6e3c --- /dev/null +++ b/chain/bitgesell/address.go @@ -0,0 +1,14 @@ +package bitgesell + +import "github.com/renproject/multichain/chain/bitcoin" + +type ( + // AddressEncoder re-exports bitcoin.AddressEncoder. + AddressEncoder = bitcoin.AddressEncoder + + // AddressDecoder re-exports bitcoin.AddressDecoder. + AddressDecoder = bitcoin.AddressDecoder + + // AddressEncodeDecoder re-exports bitcoin.AddressEncodeDecoder. + AddressEncodeDecoder = bitcoin.AddressEncodeDecoder +) diff --git a/chain/bitgesell/address_test.go b/chain/bitgesell/address_test.go new file mode 100644 index 00000000..ced13c60 --- /dev/null +++ b/chain/bitgesell/address_test.go @@ -0,0 +1 @@ +package bitgesell_test diff --git a/chain/bitgesell/bitgesell.go b/chain/bitgesell/bitgesell.go new file mode 100644 index 00000000..7c0eda63 --- /dev/null +++ b/chain/bitgesell/bitgesell.go @@ -0,0 +1,82 @@ +package bitgesell + +import ( + "github.com/bitgesellofficial/bgld/chaincfg" + "github.com/renproject/multichain/chain/bitcoin" +) + +func init() { + if err := chaincfg.Register(&MainNetParams); err != nil { + panic(err) + } + if err := chaincfg.Register(&TestNetParams); err != nil { + panic(err) + } + if err := chaincfg.Register(&RegressionNetParams); err != nil { + panic(err) + } +} + +// MainNetParams returns the chain configuration for mainnet. +var MainNetParams = chaincfg.Params{ + Name: "mainnet", + Net: 0x8ab491e8, + + // Address encoding magics + PubKeyHashAddrID: 10, + ScriptHashAddrID: 25, + PrivateKeyID: 128, + + // BIP32 hierarchical deterministic extended key magics + HDPrivateKeyID: [4]byte{0x02, 0xfa, 0xc3, 0x98}, // starts with xprv + HDPublicKeyID: [4]byte{0x02, 0xfa, 0xca, 0xfd}, // starts with xpub + + // Human-readable part for Bech32 encoded segwit addresses, as defined in + // BIP 173. + Bech32HRPSegwit: "bgl", +} + +// TestNetParams returns the chain configuration for testnet. +var TestNetParams = chaincfg.Params{ + Name: "testnet", + Net: 0xc2b5d9e6, + + // Address encoding magics + PubKeyHashAddrID: 34, + ScriptHashAddrID: 50, + PrivateKeyID: 239, + + // BIP32 hierarchical deterministic extended key magics + HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with xprv + HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with xpub + + // Human-readable part for Bech32 encoded segwit addresses, as defined in + // BIP 173. + Bech32HRPSegwit: "tbgl", +} + +// RegressionNetParams returns the chain configuration for regression net. +var RegressionNetParams = chaincfg.Params{ + Name: "regtest", + Net: 0xd98cbfba, + + // Address encoding magics + PubKeyHashAddrID: 34, + ScriptHashAddrID: 50, + PrivateKeyID: 239, + + // BIP32 hierarchical deterministic extended key magics + HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with xprv + HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with xpub + + // Human-readable part for Bech32 encoded segwit addresses, as defined in + // BIP 173. + Bech32HRPSegwit: "rbgl", +} + +// DefaultClientOptions returns ClientOptions with the default settings. These +// settings are valid for use with the default local deployment of the +// multichain. In production, the host, user, and password should be changed. +func DefaultClientOptions() ClientOptions { + return bitcoin.DefaultClientOptions().WithHost("http://0.0.0.0:18475") +} diff --git a/chain/bitgesell/bitgesell_suite_test.go b/chain/bitgesell/bitgesell_suite_test.go new file mode 100644 index 00000000..4f6d3190 --- /dev/null +++ b/chain/bitgesell/bitgesell_suite_test.go @@ -0,0 +1,13 @@ +package bitgesell_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestBitgesell(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Bitgesell Suite") +} diff --git a/chain/bitgesell/bitgesell_test.go b/chain/bitgesell/bitgesell_test.go new file mode 100644 index 00000000..e0ae5268 --- /dev/null +++ b/chain/bitgesell/bitgesell_test.go @@ -0,0 +1,138 @@ +package bitgesell_test + +import ( + "context" + "log" + "os" + "reflect" + "time" + + "github.com/bitgesellofficial/bgld/chaincfg" + "github.com/bitgesellofficial/bglutil" + "github.com/renproject/id" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/utxo" + "github.com/renproject/multichain/chain/bitgesell" + "github.com/renproject/pack" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Bitgesell", func() { + Context("when submitting transactions", func() { + Context("when sending BGL to multiple addresses", func() { + It("should work", func() { + // Load private key, and assume that the associated address has + // funds to spend. You can do this by setting BGL_PK to the + // value specified in the `./multichaindeploy/.env` file. + pkEnv := os.Getenv("BGL_PK") + if pkEnv == "" { + panic("BGL_PK is undefined") + } + wif, err := bglutil.DecodeWIF(pkEnv) + Expect(err).ToNot(HaveOccurred()) + + // note: legacy (non-witness) addresses are not supported in Bitgesell + // WPKH + wpkAddr, err := bglutil.NewAddressWitnessPubKeyHash([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}, &bitgesell.RegressionNetParams) + Expect(err).ToNot(HaveOccurred()) + log.Printf("WPKH %v", wpkAddr.EncodeAddress()) + + // Setup the client and load the unspent transaction outputs. + client := bitgesell.NewClient(bitgesell.DefaultClientOptions()) + outputs, err := client.UnspentOutputs(context.Background(), 0, 999999999, address.Address(os.Getenv("BGL_ADDRESS"))) + Expect(err).ToNot(HaveOccurred()) + Expect(len(outputs)).To(BeNumerically(">", 0)) + output := outputs[0] + + // Check that we can load the output and that it is equal. + // Otherwise, something strange is happening with the RPC + // client. + output2, _, err := client.Output(context.Background(), output.Outpoint) + Expect(err).ToNot(HaveOccurred()) + Expect(reflect.DeepEqual(output, output2)).To(BeTrue()) + output2, _, err = client.UnspentOutput(context.Background(), output.Outpoint) + Expect(err).ToNot(HaveOccurred()) + Expect(reflect.DeepEqual(output, output2)).To(BeTrue()) + + // Build the transaction by consuming the outputs and spending + // them to a set of recipients. + inputs := []utxo.Input{ + {Output: utxo.Output{ + Outpoint: utxo.Outpoint{ + Hash: output.Outpoint.Hash[:], + Index: output.Outpoint.Index, + }, + PubKeyScript: output.PubKeyScript, + Value: output.Value, + }}, + } + recipients := []utxo.Recipient{ + { + To: address.Address(os.Getenv("BGL_ADDRESS")), + Value: pack.NewU256FromU64(pack.NewU64((output.Value.Int().Uint64() - 1000) / 2)), + }, + { + To: address.Address(wpkAddr.EncodeAddress()), + Value: pack.NewU256FromU64(pack.NewU64((output.Value.Int().Uint64() - 1000) / 2)), + }, + } + tx, err := bitgesell.NewTxBuilder(&chaincfg.RegressionNetParams).BuildTx(inputs, recipients) + Expect(err).ToNot(HaveOccurred()) + + // Get the digests that need signing from the transaction, and + // sign them. In production, this would be done using the RZL + // MPC algorithm, but for the purposes of this test, using an + // explicit privkey is ok. + sighashes, err := tx.Sighashes() + + signatures := make([]pack.Bytes65, len(sighashes)) + Expect(err).ToNot(HaveOccurred()) + for i := range sighashes { + hash := id.Hash(sighashes[i]) + privKey := (*id.PrivKey)(wif.PrivKey) + signature, err := privKey.Sign(&hash) + Expect(err).ToNot(HaveOccurred()) + signatures[i] = pack.NewBytes65(signature) + } + Expect(tx.Sign(signatures, pack.NewBytes(wif.SerializePubKey()))).To(Succeed()) + + // Submit the transaction to the Bitgesell node. Again, this + // should be running a la `./multichaindeploy`. + txHash, err := tx.Hash() + Expect(err).ToNot(HaveOccurred()) + err = client.SubmitTx(context.Background(), tx) + Expect(err).ToNot(HaveOccurred()) + log.Printf("TXID %v", txHash) + + for { + // Loop until the transaction has at least a few + // confirmations. This implies that the transaction is + // definitely valid, and the test has passed. We were + // successfully able to use the multichain to construct and + // submit a Bitgesell transaction! + log.Printf("Checking for confirmations...") + confs, err := client.Confirmations(context.Background(), txHash) + Expect(err).ToNot(HaveOccurred()) + log.Printf(" %v/3 confirmations", confs) + if confs >= 1 { + break + } + time.Sleep(10 * time.Second) + } + ctxWithTimeout, cancelCtxWithTimeout := context.WithTimeout(context.Background(), time.Second) + defer cancelCtxWithTimeout() + _, _, err = client.UnspentOutput(ctxWithTimeout, output.Outpoint) + Expect(err).To(HaveOccurred()) + + // Check that we can load the output and that it is equal. + // Otherwise, something strange is happening with the RPC + // client. + output2, _, err = client.Output(context.Background(), output.Outpoint) + Expect(err).ToNot(HaveOccurred()) + Expect(reflect.DeepEqual(output, output2)).To(BeTrue()) + }) + }) + }) +}) diff --git a/chain/bitgesell/gas.go b/chain/bitgesell/gas.go new file mode 100644 index 00000000..9d9d6dd7 --- /dev/null +++ b/chain/bitgesell/gas.go @@ -0,0 +1,9 @@ +package bitgesell + +import "github.com/renproject/multichain/chain/bitcoin" + +// GasEstimator re-exports bitcoin.GasEstimator. +type GasEstimator = bitcoin.GasEstimator + +// NewGasEstimator re-exports bitcoin.NewGasEstimator. +var NewGasEstimator = bitcoin.NewGasEstimator diff --git a/chain/bitgesell/gas_test.go b/chain/bitgesell/gas_test.go new file mode 100644 index 00000000..2857dc87 --- /dev/null +++ b/chain/bitgesell/gas_test.go @@ -0,0 +1,52 @@ +package bitgesell_test + +import ( + "context" + + "github.com/renproject/multichain/chain/bitgesell" + "github.com/renproject/pack" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Gas", func() { + Context("when estimating Bitgesell network fee", func() { + It("should work", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := bitgesell.NewClient(bitgesell.DefaultClientOptions()) + + // estimate fee to include tx within 1 block. + fallback1 := uint64(123) + gasEstimator1 := bitgesell.NewGasEstimator(client, 1, pack.NewU256FromUint64(fallback1)) + gasPrice1, _, err := gasEstimator1.EstimateGas(ctx) + if err != nil { + Expect(gasPrice1).To(Equal(pack.NewU256FromUint64(fallback1))) + } + + // estimate fee to include tx within 10 blocks. + fallback2 := uint64(234) + gasEstimator2 := bitgesell.NewGasEstimator(client, 10, pack.NewU256FromUint64(fallback2)) + gasPrice2, _, err := gasEstimator2.EstimateGas(ctx) + if err != nil { + Expect(gasPrice2).To(Equal(pack.NewU256FromUint64(fallback2))) + } + + // estimate fee to include tx within 100 blocks. + fallback3 := uint64(345) + gasEstimator3 := bitgesell.NewGasEstimator(client, 100, pack.NewU256FromUint64(fallback3)) + gasPrice3, _, err := gasEstimator3.EstimateGas(ctx) + if err != nil { + Expect(gasPrice3).To(Equal(pack.NewU256FromUint64(fallback3))) + } + + // expect fees in this order at the very least. + if err == nil { + Expect(gasPrice1.GreaterThanEqual(gasPrice2)).To(BeTrue()) + Expect(gasPrice2.GreaterThanEqual(gasPrice3)).To(BeTrue()) + } + }) + }) +}) diff --git a/chain/bitgesell/utxo.go b/chain/bitgesell/utxo.go new file mode 100644 index 00000000..a10329e7 --- /dev/null +++ b/chain/bitgesell/utxo.go @@ -0,0 +1,240 @@ +package bitgesell + +import ( + "bytes" + "fmt" + "math/big" + "unsafe" + + btccfg "github.com/btcsuite/btcd/chaincfg" + "github.com/renproject/multichain/chain/bitcoin" + "github.com/bitgesellofficial/bgld/btcec" + "github.com/bitgesellofficial/bgld/chaincfg" + "github.com/bitgesellofficial/bgld/chaincfg/chainhash" + "github.com/bitgesellofficial/bgld/txscript" + "github.com/bitgesellofficial/bgld/wire" + "github.com/bitgesellofficial/bglutil" + "github.com/renproject/multichain/api/utxo" + "github.com/renproject/pack" +) + +type ( + // Client re-exports bitcoin.Client. + Client = bitcoin.Client + + // ClientOptions re-exports bitcoin.ClientOptions. + ClientOptions = bitcoin.ClientOptions +) + +var ( + // NewClient re-exports bitcoin.NewClient. + NewClient = bitcoin.NewClient +) + +// Version of Bitcoin transactions supported by the multichain. +const Version int32 = 2 + +// The TxBuilder is an implementation of a UTXO-compatible transaction builder +// for Bitcoin. +type TxBuilder struct { + params *chaincfg.Params +} + +// NewTxBuilder returns a transaction builder that builds UTXO-compatible +// Bitcoin transactions for the given chain configuration (this means that it +// can be used for regnet, testnet, and mainnet, but also for networks that are +// minimally modified forks of the Bitcoin network). +func NewBGLTxBuilder(params *chaincfg.Params) TxBuilder { + return TxBuilder{params: params} +} + +// We map BTC chain params to BGL chain params to keep namespaces separated just in case +// for now the mapping is exactly the same we can use unsafe pointer cast to keep things concise +func NewTxBuilder(params *btccfg.Params) TxBuilder { + bglparamsPtr := (*chaincfg.Params)(unsafe.Pointer(¶ms)) + return NewBGLTxBuilder(bglparamsPtr) +} + +// BuildTx returns a Bitcoin transaction that consumes funds from the given +// inputs, and sends them to the given recipients. The difference in the sum +// value of the inputs and the sum value of the recipients is paid as a fee to +// the Bitcoin network. This fee must be calculated independently of this +// function. Outputs produced for recipients will use P2PKH, P2SH, P2WPKH, or +// P2WSH scripts as the pubkey script, based on the format of the recipient +// address. +func (txBuilder TxBuilder) BuildTx(inputs []utxo.Input, recipients []utxo.Recipient) (utxo.Tx, error) { + msgTx := wire.NewMsgTx(Version) + + // Inputs + for _, input := range inputs { + hash := chainhash.Hash{} + copy(hash[:], input.Hash) + index := input.Index.Uint32() + msgTx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(&hash, index), nil, nil)) + } + + // Outputs + for _, recipient := range recipients { + addr, err := bglutil.DecodeAddress(string(recipient.To), txBuilder.params) + if err != nil { + return nil, err + } + script, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, err + } + value := recipient.Value.Int().Int64() + if value < 0 { + return nil, fmt.Errorf("expected value >= 0, got value %v", value) + } + msgTx.AddTxOut(wire.NewTxOut(value, script)) + } + + return &Tx{inputs: inputs, recipients: recipients, msgTx: msgTx, signed: false}, nil +} + +// Inputs returns the UTXO inputs in the un +// Tx represents a simple Bitcoin transaction that implements the Bitcoin Compat +// API. +type Tx struct { + inputs []utxo.Input + recipients []utxo.Recipient + + msgTx *wire.MsgTx + + signed bool +} + +// Hash returns the transaction hash of the given underlying transaction. +func (tx *Tx) Hash() (pack.Bytes, error) { + txhash := tx.msgTx.TxHash() + return pack.NewBytes(txhash[:]), nil +} + +// Inputs returns the UTXO inputs in the underlying transaction. +func (tx *Tx) Inputs() ([]utxo.Input, error) { + return tx.inputs, nil +} + +// Outputs returns the UTXO outputs in the underlying transaction. +func (tx *Tx) Outputs() ([]utxo.Output, error) { + hash, err := tx.Hash() + if err != nil { + return nil, fmt.Errorf("bad hash: %v", err) + } + outputs := make([]utxo.Output, len(tx.msgTx.TxOut)) + for i := range outputs { + outputs[i].Outpoint = utxo.Outpoint{ + Hash: hash, + Index: pack.NewU32(uint32(i)), + } + outputs[i].PubKeyScript = pack.Bytes(tx.msgTx.TxOut[i].PkScript) + if tx.msgTx.TxOut[i].Value < 0 { + return nil, fmt.Errorf("bad output %v: value is less than zero", i) + } + outputs[i].Value = pack.NewU256FromU64(pack.NewU64(uint64(tx.msgTx.TxOut[i].Value))) + } + return outputs, nil +} + +// Sighashes returns the digests that must be signed before the transaction +// can be submitted by the client. +func (tx *Tx) Sighashes() ([]pack.Bytes32, error) { + sighashes := make([]pack.Bytes32, len(tx.inputs)) + + for i, txin := range tx.inputs { + pubKeyScript := txin.PubKeyScript + sigScript := txin.SigScript + value := txin.Value.Int().Int64() + if value < 0 { + return []pack.Bytes32{}, fmt.Errorf("expected value >= 0, got value %v", value) + } + + var hash []byte + var err error + if sigScript == nil { + if txscript.IsPayToWitnessPubKeyHash(pubKeyScript) { + hash, err = txscript.CalcWitnessSigHash(pubKeyScript, txscript.NewTxSigHashes(tx.msgTx), txscript.SigHashAll, tx.msgTx, i, value) + } else { + hash, err = txscript.CalcSignatureHash(pubKeyScript, txscript.SigHashAll, tx.msgTx, i) + } + } else { + if txscript.IsPayToWitnessScriptHash(pubKeyScript) { + hash, err = txscript.CalcWitnessSigHash(sigScript, txscript.NewTxSigHashes(tx.msgTx), txscript.SigHashAll, tx.msgTx, i, value) + } else { + hash, err = txscript.CalcSignatureHash(sigScript, txscript.SigHashAll, tx.msgTx, i) + } + } + if err != nil { + return []pack.Bytes32{}, err + } + + sighash := [32]byte{} + copy(sighash[:], hash) + sighashes[i] = pack.NewBytes32(sighash) + } + + return sighashes, nil +} + +// Sign consumes a list of signatures, and adds them to the list of UTXOs in +// the underlying transactions. +func (tx *Tx) Sign(signatures []pack.Bytes65, pubKey pack.Bytes) error { + if tx.signed { + return fmt.Errorf("already signed") + } + if len(signatures) != len(tx.msgTx.TxIn) { + return fmt.Errorf("expected %v signatures, got %v signatures", len(tx.msgTx.TxIn), len(signatures)) + } + + for i, rsv := range signatures { + var err error + + // Decode the signature and the pubkey script. + r := new(big.Int).SetBytes(rsv[:32]) + s := new(big.Int).SetBytes(rsv[32:64]) + signature := btcec.Signature{ + R: r, + S: s, + } + pubKeyScript := tx.inputs[i].Output.PubKeyScript + sigScript := tx.inputs[i].SigScript + + // Support segwit. + if sigScript == nil { + if txscript.IsPayToWitnessPubKeyHash(pubKeyScript) || txscript.IsPayToWitnessScriptHash(pubKeyScript) { + tx.msgTx.TxIn[i].Witness = wire.TxWitness([][]byte{append(signature.Serialize(), byte(txscript.SigHashAll)), pubKey}) + continue + } + } else { + if txscript.IsPayToWitnessScriptHash(sigScript) || txscript.IsPayToWitnessScriptHash(sigScript) { + tx.msgTx.TxIn[i].Witness = wire.TxWitness([][]byte{append(signature.Serialize(), byte(txscript.SigHashAll)), pubKey, sigScript}) + continue + } + } + + // Support non-segwit + builder := txscript.NewScriptBuilder() + builder.AddData(append(signature.Serialize(), byte(txscript.SigHashAll))) + builder.AddData(pubKey) + if sigScript != nil { + builder.AddData(sigScript) + } + tx.msgTx.TxIn[i].SignatureScript, err = builder.Script() + if err != nil { + return err + } + } + + tx.signed = true + return nil +} + +// Serialize serializes the UTXO transaction to bytes +func (tx *Tx) Serialize() (pack.Bytes, error) { + buf := new(bytes.Buffer) + if err := tx.msgTx.Serialize(buf); err != nil { + return pack.Bytes{}, err + } + return pack.NewBytes(buf.Bytes()), nil +} diff --git a/chain/bitgesell/utxo_test.go b/chain/bitgesell/utxo_test.go new file mode 100644 index 00000000..ced13c60 --- /dev/null +++ b/chain/bitgesell/utxo_test.go @@ -0,0 +1 @@ +package bitgesell_test diff --git a/go.mod b/go.mod index 8f00b974..ade763f4 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,13 @@ module github.com/renproject/multichain go 1.14 require ( + github.com/bitgesellofficial/bgld v0.21.0-beta.0.20210312144945-d3909f58a3c3 // indirect + github.com/bitgesellofficial/bglutil v1.0.3-0.20210312144754-a8c5831c0fef // indirect github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 github.com/codahale/blake2 v0.0.0-20150924215134-8d10d0420cbf github.com/cosmos/cosmos-sdk v0.39.1 + github.com/ebfe/keccak v0.0.0-20150115210727-5cc570678d1b // indirect github.com/ethereum/go-ethereum v1.9.20 github.com/filecoin-project/go-address v0.0.4 github.com/filecoin-project/go-jsonrpc v0.1.2-0.20201008195726-68c6a2704e49 diff --git a/infra/.env b/infra/.env index 92ced4f5..f190d714 100644 --- a/infra/.env +++ b/infra/.env @@ -25,6 +25,16 @@ export BITCOIN_ADDRESS=mwjUmhAW68zCtgZpW5b1xD5g7MZew6xPV4 export BITCOINCASH_PK=cSEohZFQLKuemNeBVrzwxniouUJJxdcx7Tm6HpspYuxraVjytieW export BITCOINCASH_ADDRESS=bchreg:qp6tejc0ghtjeejcxa97amzvxvzacjt4qczpy2n3gf +# +# Bitgesell +# + +# Address that will receive mining rewards. Generally, this is set to an address +# for which the private key is known by a test suite. This allows the test suite +# access to plenty of testing funds. +export BGL_PK=cTt5qGRNCvmTNDy5ZLNXW8qhQvuiVhJBNey4CfmpRm9rEXmJoqwz +export BGL_ADDRESS=rbgl1qmukahf5eq2sw3nmhymg4xf2t3uk7fkna6h4jsr + # # DigiByte # diff --git a/infra/bitgesell/BGL.conf b/infra/bitgesell/BGL.conf new file mode 100644 index 00000000..db039118 --- /dev/null +++ b/infra/bitgesell/BGL.conf @@ -0,0 +1,10 @@ +daemon=1 +regtest=1 +rpcuser=user +rpcpassword=password +rpcallowip=0.0.0.0/0 +server=1 +txindex=1 + +[regtest] +rpcbind=0.0.0.0 \ No newline at end of file diff --git a/infra/bitgesell/Dockerfile b/infra/bitgesell/Dockerfile new file mode 100644 index 00000000..3a61e1a9 --- /dev/null +++ b/infra/bitgesell/Dockerfile @@ -0,0 +1,14 @@ +FROM ubuntu:bionic + +RUN apt-get update --fix-missing && apt-get install --yes software-properties-common wget + +RUN wget -c https://github.com/wu-emma/bitgesell/releases/download/0.1.3/bitgesell_0.1.3_amd64.deb +RUN apt-get install -y ./bitgesell_0.1.3_amd64.deb + +COPY BGL.conf /root/.BGL/BGL.conf +COPY run.sh /root/run.sh +RUN chmod +x /root/run.sh + +EXPOSE 18475 + +ENTRYPOINT ["./root/run.sh"] diff --git a/infra/bitgesell/keygen.go b/infra/bitgesell/keygen.go new file mode 100644 index 00000000..29add224 --- /dev/null +++ b/infra/bitgesell/keygen.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + + "github.com/bitgesellofficial/bgld/btcec" + "github.com/bitgesellofficial/bglutil" + + "github.com/renproject/id" + "github.com/renproject/multichain/chain/bitgesell" +) + +func main() { + privKey := id.NewPrivKey() + wif, err := bglutil.NewWIF((*btcec.PrivateKey)(privKey), &bitgesell.RegressionNetParams, true) + if err != nil { + panic(err) + } + addrPubKeyHash, err := bglutil.NewAddressPubKeyHash(bglutil.Hash160(wif.SerializePubKey()), &bitgesell.RegressionNetParams) + if err != nil { + panic(err) + } + fmt.Printf("BGL_PK=%v\n", wif) + fmt.Printf("BGL_ADDRESS=%v\n", addrPubKeyHash) +} diff --git a/infra/bitgesell/run.sh b/infra/bitgesell/run.sh new file mode 100644 index 00000000..906be200 --- /dev/null +++ b/infra/bitgesell/run.sh @@ -0,0 +1,22 @@ +#!/bin/bash +ADDRESS=$1 + +# Start +BGLd -conf=/root/.BGL/BGL.conf # -server -rpcbind=0.0.0.0 -rpcallowip=0.0.0.0/0 -rpcuser=user -rpcpassword=password +sleep 10 + +# Print setup +echo "BGL_ADDRESS=$ADDRESS" + +# Import the address +BGL-cli importaddress $ADDRESS + +# Generate enough block to pass the maturation time (100 blocks) +BGL-cli generatetoaddress 101 $ADDRESS + +# Simulate mining +while : +do + BGL-cli generatetoaddress 1 $ADDRESS + sleep 10 +done \ No newline at end of file diff --git a/infra/docker-compose.yaml b/infra/docker-compose.yaml index d558c1cd..30f9311b 100644 --- a/infra/docker-compose.yaml +++ b/infra/docker-compose.yaml @@ -51,6 +51,18 @@ services: - "./root/run.sh" - "${BITCOINCASH_ADDRESS}" + # + # Bitgesell + # + bitgesell: + build: + context: ./bitgesell + ports: + - "0.0.0.0:18475:18475" + entrypoint: + - "./root/run.sh" + - "${BGL_ADDRESS}" + # # DigiByte # diff --git a/multichain.go b/multichain.go index b2c7bd38..dc3175ae 100644 --- a/multichain.go +++ b/multichain.go @@ -100,6 +100,7 @@ type Asset string // enumerated values. Assets must be listed in alphabetical order. const ( BCH = Asset("BCH") // Bitcoin Cash + BGL = Asset("BGL") // Bitgesell BNB = Asset("BNB") // Binance Coin BTC = Asset("BTC") // Bitcoin CELO = Asset("CELO") // Celo @@ -126,6 +127,8 @@ func (asset Asset) OriginChain() Chain { switch asset { case BCH: return BitcoinCash + case BGL: + return Bitgesell case BNB: return BinanceSmartChain case BTC: @@ -167,7 +170,7 @@ func (asset Asset) OriginChain() Chain { // ChainType returns the chain-type (Account or UTXO) for the given asset func (asset Asset) ChainType() ChainType { switch asset { - case BCH, BTC, DGB, DOGE, ZEC: + case BCH, BGL, BTC, DGB, DOGE, ZEC: return ChainTypeUTXOBased case BNB, ETH, FIL, LUNA: return ChainTypeAccountBased @@ -212,6 +215,7 @@ const ( BinanceSmartChain = Chain("BinanceSmartChain") Bitcoin = Chain("Bitcoin") BitcoinCash = Chain("BitcoinCash") + Bitgesell = Chain("Bitgesell") Celo = Chain("Celo") DigiByte = Chain("DigiByte") Dogecoin = Chain("Dogecoin") @@ -252,7 +256,7 @@ func (chain *Chain) Unmarshal(buf []byte, rem int) ([]byte, int, error) { // for the chain. func (chain Chain) ChainType() ChainType { switch chain { - case Bitcoin, BitcoinCash, DigiByte, Dogecoin, Zcash: + case Bitcoin, Bitgesell, BitcoinCash, DigiByte, Dogecoin, Zcash: return ChainTypeUTXOBased case BinanceSmartChain, Ethereum, Fantom, Filecoin, Solana, Terra: return ChainTypeAccountBased @@ -292,6 +296,8 @@ func (chain Chain) NativeAsset() Asset { return BCH case Bitcoin: return BTC + case Bitgesell: + return BGL case DigiByte: return DGB case Dogecoin: diff --git a/multichain_test.go b/multichain_test.go index 220e702e..5da9bde5 100644 --- a/multichain_test.go +++ b/multichain_test.go @@ -29,6 +29,7 @@ import ( "github.com/renproject/multichain/chain/bitcoincash" // "github.com/renproject/multichain/chain/digibyte" + "github.com/renproject/multichain/chain/bitgesell" "github.com/renproject/multichain/chain/dogecoin" "github.com/renproject/multichain/chain/filecoin" "github.com/renproject/multichain/chain/terra" @@ -46,6 +47,7 @@ import ( var ( testBTC = flag.Bool("btc", false, "Pass this flag to test Bitcoin") testBCH = flag.Bool("bch", false, "Pass this flag to test Bitcoincash") + testBGL = flag.Bool("bgl", false, "Pass this flag to test Bitgesell") testDOGE = flag.Bool("doge", false, "Pass this flag to test Dogecoin") testFIL = flag.Bool("fil", false, "Pass this flag to test Filecoin") testLUNA = flag.Bool("luna", false, "Pass this flag to test Terra") @@ -69,6 +71,7 @@ var _ = Describe("Multichain", func() { testFlags := map[multichain.Chain]bool{} testFlags[multichain.Bitcoin] = *testBTC testFlags[multichain.BitcoinCash] = *testBCH + testFlags[multichain.Bitgesell] = *testBGL testFlags[multichain.Dogecoin] = *testDOGE testFlags[multichain.Filecoin] = *testFIL testFlags[multichain.Terra] = *testLUNA @@ -112,6 +115,10 @@ var _ = Describe("Multichain", func() { multichain.BitcoinCash, multichain.BCH, }, + { + multichain.Bitgesell, + multichain.BGL, + }, { multichain.DigiByte, multichain.DGB, @@ -162,8 +169,63 @@ var _ = Describe("Multichain", func() { func() multichain.AddressEncodeDecoder { addrEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) return addrEncodeDecoder + }, + func() multichain.Address { + // Generate a random SECP256K1 private key. + pk := id.NewPrivKey() + // Get bitcoin WIF private key with the pub key configured to be in + // the compressed form. + wif, err := btcutil.NewWIF((*btcec.PrivateKey)(pk), &chaincfg.RegressionNetParams, true) + Expect(err).NotTo(HaveOccurred()) + addrPubKeyHash, err := btcutil.NewAddressPubKeyHash(btcutil.Hash160(wif.SerializePubKey()), &chaincfg.RegressionNetParams) + Expect(err).NotTo(HaveOccurred()) + // Return the human-readable encoded bitcoin address in base58 format. + return multichain.Address(addrPubKeyHash.EncodeAddress()) + }, + func() multichain.RawAddress { + // Generate a random SECP256K1 private key. + pk := id.NewPrivKey() + // Get bitcoin WIF private key with the pub key configured to be in + // the compressed form. + wif, err := btcutil.NewWIF((*btcec.PrivateKey)(pk), &chaincfg.RegressionNetParams, true) + Expect(err).NotTo(HaveOccurred()) + // Get the address pubKey hash. This is the most commonly used format + // for a bitcoin address. + addrPubKeyHash, err := btcutil.NewAddressPubKeyHash(btcutil.Hash160(wif.SerializePubKey()), &chaincfg.RegressionNetParams) + Expect(err).NotTo(HaveOccurred()) + // Encode into the checksummed base58 format. + encoded := addrPubKeyHash.EncodeAddress() + return multichain.RawAddress(pack.Bytes(base58.Decode(encoded))) }, func() multichain.Address { + // Random bytes of script. + script := make([]byte, r.Intn(100)) + r.Read(script) + // Create address script hash from the random script bytes. + addrScriptHash, err := btcutil.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) + Expect(err).NotTo(HaveOccurred()) + // Return in human-readable encoded form. + return multichain.Address(addrScriptHash.EncodeAddress()) + }, + func() multichain.RawAddress { + // Random bytes of script. + script := make([]byte, r.Intn(100)) + r.Read(script) + // Create address script hash from the random script bytes. + addrScriptHash, err := btcutil.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) + Expect(err).NotTo(HaveOccurred()) + // Encode to the checksummed base58 format. + encoded := addrScriptHash.EncodeAddress() + return multichain.RawAddress(pack.Bytes(base58.Decode(encoded))) + }, + }, + { + multichain.Bitgesell, + func() multichain.AddressEncodeDecoder { + addrEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) + return addrEncodeDecoder + }, + func() multichain.Address { // Generate a random SECP256K1 private key. pk := id.NewPrivKey() // Get bitcoin WIF private key with the pub key configured to be in @@ -727,6 +789,26 @@ var _ = Describe("Multichain", func() { bitcoincash.NewTxBuilder(&chaincfg.RegressionNetParams), multichain.BitcoinCash, }, + { + "BITGESELL_PK", + func(pkh []byte) (btcutil.Address, error) { + addr, err := btcutil.NewAddressPubKeyHash(pkh, &chaincfg.RegressionNetParams) + return addr, err + }, + func(script []byte) (btcutil.Address, error) { + addr, err := btcutil.NewAddressScriptHash(script, &chaincfg.RegressionNetParams) + return addr, err + }, + pack.NewString("http://0.0.0.0:18475"), + func(rpcURL pack.String, pkhAddr btcutil.Address) (multichain.UTXOClient, []multichain.UTXOutput, func(context.Context, pack.Bytes) (int64, error)) { + client := bitgesell.NewClient(bitgesell.DefaultClientOptions()) + outputs, err := client.UnspentOutputs(ctx, 0, 999999999, multichain.Address(pkhAddr.EncodeAddress())) + Expect(err).NotTo(HaveOccurred()) + return client, outputs, client.Confirmations + }, + bitgesell.NewTxBuilder(&chaincfg.RegressionNetParams), + multichain.Bitgesell, + }, { "DOGECOIN_PK", func(pkh []byte) (btcutil.Address, error) {