Skip to content

Commit

Permalink
manual median times. more nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed Jun 11, 2022
1 parent fc341d6 commit 6bab295
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 77 deletions.
15 changes: 6 additions & 9 deletions client/asset/btc/btc.go
Expand Up @@ -292,6 +292,8 @@ type BTCCloneCFG struct {
// TxVersion is an optional function that returns a version to use for
// new transactions.
TxVersion func() int32
// ManualMedianTime causes the median time to be calculated manually.
ManualMedianTime bool
}

// outPoint is the hash and output index of a transaction output.
Expand Down Expand Up @@ -868,6 +870,7 @@ func newRPCWallet(requester RawRequesterWithContext, cfg *BTCCloneCFG, walletCon
hashTx: btc.hashTx,
numericGetRawTxRPC: cfg.NumericGetRawRPC,
legacyValidateAddressRPC: cfg.LegacyValidateAddressRPC,
manualMedianTime: cfg.ManualMedianTime,
})
return &ExchangeWalletFullNode{btc}, nil
}
Expand Down Expand Up @@ -3123,16 +3126,11 @@ func (btc *baseWallet) LocktimeExpired(contract dex.Bytes) (bool, time.Time, err
return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err)
}
contractExpiry := time.Unix(int64(locktime), 0).UTC()
bestBlockHash, err := btc.node.getBestBlockHash()
if err != nil {
return false, time.Time{}, fmt.Errorf("get best block hash error: %w", err)
}
bestBlockHeader, err := btc.node.getBlockHeader(bestBlockHash)
medianTime, err := btc.node.medianTime()
if err != nil {
return false, time.Time{}, fmt.Errorf("get best block header error: %w", err)
return false, time.Time{}, fmt.Errorf("error getting median time: %w", err)
}
bestBlockMedianTime := time.Unix(bestBlockHeader.MedianTime, 0).UTC()
return bestBlockMedianTime.After(contractExpiry), contractExpiry, nil
return medianTime.After(contractExpiry), contractExpiry, nil
}

// FindRedemption watches for the input that spends the specified contract
Expand Down Expand Up @@ -4298,7 +4296,6 @@ type blockHeader struct {
Confirmations int64 `json:"confirmations"`
Height int64 `json:"height"`
Time int64 `json:"time"`
MedianTime int64 `json:"mediantime"`
PreviousBlockHash string `json:"previousblockhash"`
}

Expand Down
7 changes: 5 additions & 2 deletions client/asset/btc/livetest/livetest.go
Expand Up @@ -115,7 +115,11 @@ func (rig *testRig) close() {
}

func (rig *testRig) mineAlpha() error {
return exec.Command("tmux", "send-keys", "-t", rig.symbol+"-harness:2", "./mine-alpha 1", "C-m").Run()
tmuxWindow := rig.symbol + "-harness:2"
if rig.symbol == "zec" {
tmuxWindow = rig.symbol + "-harness:4"
}
return exec.Command("tmux", "send-keys", "-t", tmuxWindow, "./mine-alpha 1", "C-m").Run()
}

func randBytes(l int) []byte {
Expand Down Expand Up @@ -190,7 +194,6 @@ func Run(t *testing.T, cfg *Config) {

t.Log("Setting up alpha/beta/gamma wallet backends...")
rig.firstWallet = tBackend(tCtx, t, cfg, cfg.FirstWallet, blkFunc)
// rig.backends["beta"], rig.connectionMasters["beta"] = tBackend(tCtx, t, cfg, "beta", "", tLogger.SubLogger("beta"), blkFunc)
rig.secondWallet = tBackend(tCtx, t, cfg, cfg.SecondWallet, blkFunc)
defer rig.close()

Expand Down
52 changes: 49 additions & 3 deletions client/asset/btc/rpcclient.go
Expand Up @@ -90,6 +90,7 @@ type rpcCore struct {
hashTx func(*wire.MsgTx) *chainhash.Hash
numericGetRawTxRPC bool
legacyValidateAddressRPC bool
manualMedianTime bool
}

// rpcClient is a bitcoind JSON RPC client that uses rpcclient.Client's
Expand Down Expand Up @@ -287,6 +288,35 @@ func (wc *rpcClient) getBestBlockHeight() (int32, error) {
return int32(header.Height), nil
}

// getChainStamp satisfies chainStamper for manual median time calculations.
func (wc *rpcClient) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) {
hdr, err := wc.getBlockHeader(blockHash)
if err != nil {
return
}
prevHash, err = chainhash.NewHashFromStr(hdr.PreviousBlockHash)
if err != nil {
return
}
return time.Unix(hdr.Time, 0).UTC(), prevHash, nil
}

// medianTime is the median time for the current best block.
func (wc *rpcClient) medianTime() (stamp time.Time, err error) {
tipHash, err := wc.getBestBlockHash()
if err != nil {
return
}
if wc.manualMedianTime {
return calcMedianTime(wc, tipHash)
}
hdr, err := wc.getRPCBlockHeader(tipHash)
if err != nil {
return
}
return time.Unix(hdr.MedianTime, 0).UTC(), nil
}

// GetRawMempool returns the hashes of all transactions in the memory pool.
func (wc *rpcClient) GetRawMempool() ([]*chainhash.Hash, error) {
var mempool []string
Expand Down Expand Up @@ -568,17 +598,33 @@ func (wc *rpcClient) swapConfirmations(txHash *chainhash.Hash, vout uint32, _ []
return uint32(tx.Confirmations), true, nil
}

// getBlockHeader gets the block header for the specified block hash.
func (wc *rpcClient) getBlockHeader(blockHash *chainhash.Hash) (*blockHeader, error) {
blkHeader := new(blockHeader)
// rpcBlockHeader adds a MedianTime field to blockHeader.
type rpcBlockHeader struct {
blockHeader
MedianTime int64 `json:"mediantime"`
}

// getBlockHeader gets the *rpcBlockHeader for the specified block hash.
func (wc *rpcClient) getRPCBlockHeader(blockHash *chainhash.Hash) (*rpcBlockHeader, error) {
blkHeader := new(rpcBlockHeader)
err := wc.call(methodGetBlockHeader,
anylist{blockHash.String(), true}, blkHeader)
if err != nil {
return nil, err
}

return blkHeader, nil
}

// getBlockHeader gets the *blockHeader for the specified block hash.
func (wc *rpcClient) getBlockHeader(blockHash *chainhash.Hash) (*blockHeader, error) {
hdr, err := wc.getRPCBlockHeader(blockHash)
if err != nil {
return nil, err
}
return &hdr.blockHeader, nil
}

// getBlockHeight gets the mainchain height for the specified block.
func (wc *rpcClient) getBlockHeight(blockHash *chainhash.Hash) (int32, error) {
hdr, err := wc.getBlockHeader(blockHash)
Expand Down
54 changes: 15 additions & 39 deletions client/asset/btc/spv.go
Expand Up @@ -521,6 +521,21 @@ func (w *spvWallet) getBestBlockHeight() (int32, error) {
return w.wallet.syncedTo().Height, nil
}

// getChainStamp satisfies chainStamper for manual median time calculations.
func (w *spvWallet) getChainStamp(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) {
hdr, err := w.cl.GetBlockHeader(blockHash)
if err != nil {
return
}
return hdr.Timestamp, &hdr.PrevBlock, nil
}

// medianTime is the median time for the current best block.
func (w *spvWallet) medianTime() (time.Time, error) {
blk := w.wallet.syncedTo()
return calcMedianTime(w, &blk.Hash)
}

// getChainHeight is only for confirmations since it does not reflect the wallet
// manager's sync height, just the chain service.
func (w *spvWallet) getChainHeight() (int32, error) {
Expand Down Expand Up @@ -1015,11 +1030,6 @@ func (w *spvWallet) getBlockHeader(blockHash *chainhash.Hash) (*blockHeader, err
return nil, err
}

medianTime, err := w.calcMedianTime(blockHash)
if err != nil {
return nil, err
}

tip, err := w.cl.BestBlock()
if err != nil {
return nil, fmt.Errorf("BestBlock error: %v", err)
Expand All @@ -1035,43 +1045,9 @@ func (w *spvWallet) getBlockHeader(blockHash *chainhash.Hash) (*blockHeader, err
Confirmations: int64(confirms(blockHeight, tip.Height)),
Height: int64(blockHeight),
Time: hdr.Timestamp.Unix(),
MedianTime: medianTime.Unix(),
}, nil
}

const medianTimeBlocks = 11

// calcMedianTime calculates the median time of the previous 11 block headers.
// The median time is used for validating time-locked transactions. See notes in
// btcd/blockchain (*blockNode).CalcPastMedianTime() regarding incorrectly
// calculated median time for blocks 1, 3, 5, 7, and 9.
func (w *spvWallet) calcMedianTime(blockHash *chainhash.Hash) (time.Time, error) {
timestamps := make([]int64, 0, medianTimeBlocks)

zeroHash := chainhash.Hash{}

h := blockHash
for i := 0; i < medianTimeBlocks; i++ {
hdr, err := w.cl.GetBlockHeader(h)
if err != nil {
return time.Time{}, fmt.Errorf("BlockHeader error for hash %q: %v", h, err)
}
timestamps = append(timestamps, hdr.Timestamp.Unix())

if hdr.PrevBlock == zeroHash {
break
}
h = &hdr.PrevBlock
}

sort.Slice(timestamps, func(i, j int) bool {
return timestamps[i] < timestamps[j]
})

medianTimestamp := timestamps[len(timestamps)/2]
return time.Unix(medianTimestamp, 0), nil
}

func (w *spvWallet) logFilePath() string {
return filepath.Join(w.netDir, logDirName, logFileName)
}
Expand Down
43 changes: 43 additions & 0 deletions client/asset/btc/wallet.go
Expand Up @@ -2,6 +2,8 @@ package btc

import (
"context"
"fmt"
"sort"
"sync"
"time"

Expand All @@ -22,6 +24,7 @@ type Wallet interface {
getBlockHeight(*chainhash.Hash) (int32, error)
getBestBlockHash() (*chainhash.Hash, error)
getBestBlockHeight() (int32, error)
medianTime() (time.Time, error)
balances() (*GetBalancesResult, error)
listUnspent() ([]*ListUnspentResult, error)
lockUnspent(unlock bool, ops []*output) error
Expand Down Expand Up @@ -49,3 +52,43 @@ type Wallet interface {
type tipNotifier interface {
tipFeed() <-chan *block
}

// chainStamper is a source of the timestamp and the previous block hash for a
// specified block. A chainStamper is used to manually calculate the median time
// for a block.
type chainStamper interface {
getChainStamp(*chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error)
}

const medianTimeBlocks = 11

// calcMedianTime calculates the median time of the previous 11 block headers.
// The median time is used for validating time-locked transactions. See notes in
// btcd/blockchain (*blockNode).CalcPastMedianTime() regarding incorrectly
// calculated median time for blocks 1, 3, 5, 7, and 9.
func calcMedianTime(stamper chainStamper, blockHash *chainhash.Hash) (time.Time, error) {
timestamps := make([]int64, 0, medianTimeBlocks)

zeroHash := chainhash.Hash{}

h := blockHash
for i := 0; i < medianTimeBlocks; i++ {
stamp, prevHash, err := stamper.getChainStamp(h)
if err != nil {
return time.Time{}, fmt.Errorf("BlockHeader error for hash %q: %v", h, err)
}
timestamps = append(timestamps, stamp.Unix())

if *prevHash == zeroHash {
break
}
h = prevHash
}

sort.Slice(timestamps, func(i, j int) bool {
return timestamps[i] < timestamps[j]
})

medianTimestamp := timestamps[len(timestamps)/2]
return time.Unix(medianTimestamp, 0), nil
}
2 changes: 2 additions & 0 deletions client/asset/zec/zec.go
Expand Up @@ -209,6 +209,8 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (ass
TxVersion: func() int32 {
return dexzec.VersionNU5
},
// https://github.com/zcash/zcash/pull/6005
ManualMedianTime: true,
}

var err error
Expand Down
2 changes: 1 addition & 1 deletion dex/testing/dcrdex/harness.sh
Expand Up @@ -140,7 +140,7 @@ if [ $ZEC_ON -eq 0 ]; then
"base": "ZEC_simnet",
"quote": "BTC_simnet",
"lotSize": 100000000,
"rateStep": 100000,
"rateStep": 1000,
"epochDuration": ${EPOCH_DURATION},
"marketBuyBuffer": 1.2
EOF
Expand Down

0 comments on commit 6bab295

Please sign in to comment.