Skip to content

Commit

Permalink
Merge pull request #126 from commerceblock/develop
Browse files Browse the repository at this point in the history
Release 1.2.0 - Timing and fee optimisations
  • Loading branch information
Nikos Kostoulas authored Nov 21, 2019
2 parents c66bff0 + 1beb417 commit 20271c8
Show file tree
Hide file tree
Showing 27 changed files with 488 additions and 338 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Mainstay
The mainstay repository is an application that implements the [Mainstay protocol](https://www.commerceblock.com/wp-content/uploads/2018/03/commerceblock-mainstay-whitepaper.pdf) designed by [CommerceBlock](https://www.commerceblock.com). It consists of a Go daemon that performs attestations of the [Ocean](https://github.com/commerceblock/ocean) network along with client commitments to Bitcoin in the form of a commitment merkle tree.
The mainstay repository is an application that implements the [Mainstay protocol](https://cloudflare-ipfs.com/ipns/ipfs.commerceblock.com/commerceblock-whitepaper-mainstay.pdf) designed by [CommerceBlock](https://www.commerceblock.com). It consists of a Go daemon that performs attestations of the [Ocean](https://github.com/commerceblock/ocean) network along with client commitments to Bitcoin in the form of a commitment merkle tree.

Mainstay is accompanied by a Confirmation tool that can be run in parallel with the Bitcoin network to confirm attestations and prove the commitment inclusion in Mainstay attestations.

Expand Down Expand Up @@ -75,4 +75,3 @@ The multisig tool `cmd/multisigtool` can be used to generate multisig scripts an
For more information go to [tool guidelines](/cmd/README.md).

For example use cases go to [docs](/doc/).

32 changes: 19 additions & 13 deletions attestation/attestclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
"encoding/hex"
"errors"
"fmt"
"log"
"math"

confpkg "mainstay/config"
"mainstay/crypto"
"mainstay/log"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcjson"
Expand Down Expand Up @@ -43,6 +43,7 @@ const (
ErrorInputMissingForTx = `Missing input for transaction`
ErrorInvalidChaincode = `Invalid chaincode provided`
ErrorMissingChaincodes = `Missing chaincodes for pubkeys`
ErrorTopUpScriptNumSigs = `Different number of signatures in Init script to top-up script`
)

// coin in satoshis
Expand Down Expand Up @@ -103,11 +104,11 @@ func parseTopupKeys(config *confpkg.Config, isSigner bool) *btcutil.WIF {
if pkTopup != "" {
topupWif, errPkWifTopup := crypto.GetWalletPrivKey(pkTopup)
if errPkWifTopup != nil {
log.Fatalf("%s %s\n%v\n", ErrorInvalidPk, pkTopup, errPkWifTopup)
log.Errorf("%s %s\n%v\n", ErrorInvalidPk, pkTopup, errPkWifTopup)
}
return topupWif
} else {
log.Println(WarningTopupPkMissing)
log.Warnln(WarningTopupPkMissing)
}
}
return nil
Expand All @@ -120,11 +121,11 @@ func parseMainKeys(config *confpkg.Config, isSigner bool) *btcutil.WIF {
pk := config.InitPK()
pkWif, errPkWif := crypto.GetWalletPrivKey(pk)
if errPkWif != nil {
log.Fatalf("%s %s\n%v\n", ErrorInvalidPk, pk, errPkWif)
log.Errorf("%s %s\n%v\n", ErrorInvalidPk, pk, errPkWif)
}
return pkWif
} else if config.InitScript() == "" {
log.Fatal(ErrorMissingMultisig)
log.Error(ErrorMissingMultisig)
}
return nil
}
Expand Down Expand Up @@ -155,16 +156,21 @@ func newMultisigAttestClient(config *confpkg.Config, isSigner bool, wif *btcutil
multisig := config.InitScript()
pubkeys, numOfSigs := crypto.ParseRedeemScript(multisig)

// Verify same amount of sigs in topupscript and init script
if _, topUpnumOfSigs := crypto.ParseRedeemScript(config.TopupScript()); numOfSigs != topUpnumOfSigs {
log.Errorf("%s. %d != %d", ErrorTopUpScriptNumSigs, numOfSigs, topUpnumOfSigs)
}

// get chaincodes of pubkeys from config
chaincodesStr := config.InitChaincodes()
if len(chaincodesStr) != len(pubkeys) {
log.Fatal(fmt.Sprintf("%s %d != %d", ErrorMissingChaincodes, len(chaincodesStr), len(pubkeys)))
log.Errorf("%s %d != %d", ErrorMissingChaincodes, len(chaincodesStr), len(pubkeys))
}
chaincodes := make([][]byte, len(pubkeys))
for i_c := range chaincodesStr {
ccBytes, ccBytesErr := hex.DecodeString(chaincodesStr[i_c])
if ccBytesErr != nil || len(ccBytes) != 32 {
log.Fatal(fmt.Sprintf("%s %s", ErrorInvalidChaincode, chaincodesStr[i_c]))
log.Errorf("%s %s", ErrorInvalidChaincode, chaincodesStr[i_c])
}
chaincodes[i_c] = append(chaincodes[i_c], ccBytes...)
}
Expand All @@ -180,7 +186,7 @@ func newMultisigAttestClient(config *confpkg.Config, isSigner bool, wif *btcutil
}
}
if !myFound {
log.Fatal(ErrorMissingAddress)
log.Error(ErrorMissingAddress)
}
}

Expand Down Expand Up @@ -229,14 +235,14 @@ func NewAttestClient(config *confpkg.Config, signerFlag ...bool) *AttestClient {
topupScriptStr := config.TopupScript()
var pkWifTopup *btcutil.WIF
if topupAddrStr != "" && topupScriptStr != "" {
log.Printf("*Client* importing top-up addr: %s ...\n", topupAddrStr)
log.Infof("*Client* importing top-up addr: %s ...\n", topupAddrStr)
importErr := config.MainClient().ImportAddressRescan(topupAddrStr, "", false)
if importErr != nil {
log.Printf("%s (%s)\n%v\n", WarningFailureImportingTopupAddress, topupAddrStr, importErr)
log.Warnf("%s (%s)\n%v\n", WarningFailureImportingTopupAddress, topupAddrStr, importErr)
}
pkWifTopup = parseTopupKeys(config, isSigner)
} else {
log.Println(WarningTopupInfoMissing)
log.Warnln(WarningTopupInfoMissing)
}

// main config
Expand Down Expand Up @@ -397,7 +403,7 @@ func (w *AttestClient) createAttestation(paytoaddr btcutil.Address, unspent []bt

// print warning if txout value less than 100*maxfee target
if msgTx.TxOut[0].Value < 100*maxFee {
log.Println(WarningInsufficientFunds)
log.Warnln(WarningInsufficientFunds)
}

// add fees using best fee-per-byte estimate
Expand Down Expand Up @@ -521,7 +527,7 @@ func (w *AttestClient) getTransactionPreImages(hash chainhash.Hash, msgTx *wire.
if len(msgTx.TxIn) > 1 {
topupScriptSer, topupDecodeErr := hex.DecodeString(w.scriptTopup)
if topupDecodeErr != nil {
log.Printf("%s %s\n", WarningFailedDecodingTopupMultisig, w.scriptTopup)
log.Warnf("%s %s\n", WarningFailedDecodingTopupMultisig, w.scriptTopup)
return preImageTxs, nil
}
for i := 1; i < len(msgTx.TxIn); i++ {
Expand Down
4 changes: 4 additions & 0 deletions attestation/attestclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,16 +585,20 @@ func TestAttestClient_feeCalculation(t *testing.T) {
_, numOfSigs := crypto.ParseRedeemScript(testpkg.Script)
assert.Equal(t, 229, calcSignedTxSize(unsignedTxSize, scriptSize, numOfSigs, 1))
assert.Equal(t, int64(2290), calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize, numOfSigs, 1))
assert.Equal(t, 10, int(calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize, numOfSigs, 1))/calcSignedTxSize(unsignedTxSize, scriptSize, numOfSigs, 1))

assert.Equal(t, 375, calcSignedTxSize(unsignedTxSize, scriptSize, numOfSigs, 2))
assert.Equal(t, int64(3750), calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize, numOfSigs, 2))
assert.Equal(t, 10, int(calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize, numOfSigs, 2))/calcSignedTxSize(unsignedTxSize, scriptSize, numOfSigs, 2))

script2 := "52210325bf82856a8fdcc7a2c08a933343d2c6332c4c252974d6b09b6232ea4080462621028ed149d77203c79d7524048689a80cc98f27e3427f2edaec52eae1f630978e08210254a548b59741ba35bfb085744373a8e10b1cf96e71f53356d7d97f807258d38c53ae"
scriptSize2 := len(script2) / 2
_, numOfSigs2 := crypto.ParseRedeemScript(script2)
assert.Equal(t, 339, calcSignedTxSize(unsignedTxSize, scriptSize2, numOfSigs2, 1))
assert.Equal(t, int64(3390), calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize2, numOfSigs2, 1))
assert.Equal(t, 10, int(calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize2, numOfSigs2, 1))/calcSignedTxSize(unsignedTxSize, scriptSize2, numOfSigs2, 1))

assert.Equal(t, 851, calcSignedTxSize(unsignedTxSize, scriptSize2, numOfSigs2, 3))
assert.Equal(t, int64(8510), calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize2, numOfSigs2, 3))
assert.Equal(t, 10, int(calcSignedTxFee(feePerByte, unsignedTxSize, scriptSize2, numOfSigs2, 3))/calcSignedTxSize(unsignedTxSize, scriptSize2, numOfSigs2, 3))
}
42 changes: 24 additions & 18 deletions attestation/attestfees.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ package attestation

import (
"encoding/json"
"log"
"net/http"

"mainstay/config"
"mainstay/log"
)

// Utility functions to get best bitcoin fees from a remote API
Expand All @@ -25,9 +25,9 @@ const (

// warnings for arguments
const (
WarningInvalidMinFeeArg = "Warning - Invalid min fee config value"
WarningInvalidMaxFeeArg = "Warning - Invalid max fee config value"
WarningInvalidFeeIncrementArg = "Warning - Invalid fee increment config value"
WarningInvalidMinFeeArg = "Invalid min fee config value"
WarningInvalidMaxFeeArg = "Invalid max fee config value"
WarningInvalidFeeIncrementArg = "Invalid fee increment config value"
)

// fee api config
Expand Down Expand Up @@ -69,27 +69,27 @@ func NewAttestFees(feesConfig config.FeesConfig) AttestFees {
if feesConfig.MinFee > 0 && feesConfig.MinFee < DefaultMaxFee {
minFee = feesConfig.MinFee
} else {
log.Printf("%s (%d)\n", WarningInvalidMinFeeArg, feesConfig.MinFee)
log.Warnf("%s (%d)\n", WarningInvalidMinFeeArg, feesConfig.MinFee)
}
log.Printf("*Fees* Min fee set to: %d\n", minFee)
log.Infof("*Fees* Min fee set to: %d\n", minFee)

// max fee with lower limit min_fee && 0 and max fee default
maxFee := DefaultMaxFee
if feesConfig.MaxFee > 0 && feesConfig.MaxFee > minFee && feesConfig.MaxFee < DefaultMaxFee {
maxFee = feesConfig.MaxFee
} else {
log.Printf("%s (%d)\n", WarningInvalidMaxFeeArg, feesConfig.MaxFee)
log.Warnf("%s (%d)\n", WarningInvalidMaxFeeArg, feesConfig.MaxFee)
}
log.Printf("*Fees* Max fee set to: %d\n", maxFee)
log.Infof("*Fees* Max fee set to: %d\n", maxFee)

// fee increment with lower limit 0
feeIncrement := DefaultFeeIncrement
if feesConfig.FeeIncrement > 0 {
feeIncrement = feesConfig.FeeIncrement
} else {
log.Printf("%s (%d)\n", WarningInvalidFeeIncrementArg, feesConfig.FeeIncrement)
log.Warnf("%s (%d)\n", WarningInvalidFeeIncrementArg, feesConfig.FeeIncrement)
}
log.Printf("*Fees* Fee increment set to: %d\n", feeIncrement)
log.Infof("*Fees* Fee increment set to: %d\n", feeIncrement)

attestFees := AttestFees{
minFee: minFee,
Expand All @@ -103,13 +103,13 @@ func NewAttestFees(feesConfig config.FeesConfig) AttestFees {

// Get current fee
func (a AttestFees) GetFee() int {
log.Printf("*Fees* Current fee value: %d\n", a.currentFee)
log.Infof("*Fees* Current fee value: %d\n", a.currentFee)
return a.currentFee
}

// Get previous fee
func (a AttestFees) GetPrevFee() int {
log.Printf("*Fees* Previous fee value: %d\n", a.prevFee)
log.Infof("*Fees* Previous fee value: %d\n", a.prevFee)
return a.prevFee
}

Expand All @@ -129,20 +129,26 @@ func (a *AttestFees) ResetFee(useMinimum ...bool) {
}
a.currentFee = fee
a.prevFee = 0
log.Printf("*Fees* Current fee set to value: %d\n", a.currentFee)
log.Infof("*Fees* Current fee set to value: %d\n", a.currentFee)
}

// Bump fee upon request using increment value and not allowing values higher than max configured fee
func (a *AttestFees) BumpFee() {
a.prevFee = a.currentFee
a.currentFee += a.feeIncrement
log.Printf("*Fees* Bumping fee value to: %d\n", a.currentFee)
log.Infof("*Fees* Bumping fee value to: %d\n", a.currentFee)
if a.currentFee > a.maxFee {
log.Printf("*Fees* Max allowed fee value reached: %d\n", a.currentFee)
log.Infof("*Fees* Max allowed fee value reached: %d\n", a.currentFee)
a.currentFee = a.maxFee
}
}

// Manually force set current fee regardless of max, min values
func (a *AttestFees) setCurrentFee(fee int) {
a.currentFee = fee
log.Infof("*Fees* Current value set to: %d\n", a.currentFee)
}

// getBestFee returns the best fee for the type requested from the API
func getBestFee(customFeeType ...string) int {
var feeType = DefaultBestFeeType
Expand All @@ -158,7 +164,7 @@ func getBestFee(customFeeType ...string) int {
func getFeeFromAPI(feeType string) int {
resp, getErr := http.Get(FeeApiUrl)
if getErr != nil {
log.Println("*Fees* API request failed")
log.Infoln("*Fees* API request failed")
return -1
}

Expand All @@ -167,13 +173,13 @@ func getFeeFromAPI(feeType string) int {
var respJson map[string]float64
decErr := dec.Decode(&respJson)
if decErr != nil {
log.Println("*Fees* API response decoding failed")
log.Infoln("*Fees* API response decoding failed")
return -1
}

fee, ok := respJson[feeType]
if !ok {
log.Println("*Fees* API response incorrect format")
log.Infoln("*Fees* API response incorrect format")
return -1
}

Expand Down
6 changes: 6 additions & 0 deletions attestation/attestfees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ func TestAttestFees(t *testing.T) {
attestFees.BumpFee()
assert.Equal(t, 90, attestFees.GetPrevFee())
assert.Equal(t, attestFees.maxFee, attestFees.GetFee())

// test setCurrentFee()
new_current := 7
assert.NotEqual(t, new_current, attestFees.currentFee)
attestFees.setCurrentFee(new_current)
assert.Equal(t, new_current, attestFees.currentFee)
}

// Attest Fees test with custom feesConfig
Expand Down
Loading

0 comments on commit 20271c8

Please sign in to comment.