Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: detect memo in btc txn from OP_RETURN and inscription #2533

Merged
merged 50 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
857af38
parse inscription like witness data
bitSmiley Jul 21, 2024
73571d7
more comment
bitSmiley Jul 21, 2024
2751889
remove unused code
bitSmiley Jul 21, 2024
0eeb657
parse inscription
bitSmiley Jul 22, 2024
f69bfa4
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 22, 2024
8bc2978
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 22, 2024
6dbb635
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 22, 2024
d8732a3
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 23, 2024
c43a053
pull origin
bitSmiley Jul 23, 2024
0b36af7
Merge branch 'feat-btc_inscription' of bitsmiley-github:bitSmiley/nod…
bitSmiley Jul 23, 2024
d2088d2
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 23, 2024
5173f05
review feedbacks
bitSmiley Jul 23, 2024
93b2c9e
Merge branch 'feat-btc_inscription' of bitsmiley-github:bitSmiley/nod…
bitSmiley Jul 23, 2024
e89fabf
update review feedbacks
bitSmiley Jul 23, 2024
bc349f1
scan op_ret then inscription
bitSmiley Jul 23, 2024
46ceef9
add mainnet txn
bitSmiley Jul 23, 2024
4467e13
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 24, 2024
9b2b051
parse inscription like witness data
bitSmiley Jul 21, 2024
5447420
more comment
bitSmiley Jul 21, 2024
d767352
remove unused code
bitSmiley Jul 21, 2024
b83f923
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 22, 2024
6fb2dfd
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 22, 2024
d00d739
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 22, 2024
1c44556
Update zetaclient/chains/bitcoin/tx_script.go
bitSmiley Jul 23, 2024
61e8700
pull origin
bitSmiley Jul 23, 2024
59e8adc
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 23, 2024
25d6c4e
review feedbacks
bitSmiley Jul 23, 2024
8790c55
update review feedbacks
bitSmiley Jul 23, 2024
b482706
update make generate
bitSmiley Jul 25, 2024
1a30f39
fix linter
bitSmiley Jul 26, 2024
06f46e9
remove over flow
bitSmiley Jul 26, 2024
47e851e
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 30, 2024
898edba
Update zetaclient/chains/bitcoin/tokenizer.go
bitSmiley Jul 30, 2024
0070841
Update zetaclient/chains/bitcoin/tokenizer.go
bitSmiley Jul 30, 2024
57716fa
Update zetaclient/chains/bitcoin/tokenizer.go
bitSmiley Jul 30, 2024
b97c226
Update zetaclient/chains/bitcoin/tokenizer.go
bitSmiley Jul 30, 2024
fb8076c
update review feedback
bitSmiley Jul 30, 2024
2dea0c5
update code commnet
bitSmiley Jul 30, 2024
c424b65
update comment
bitSmiley Jul 30, 2024
abf043d
more comments
bitSmiley Jul 30, 2024
27a3ca2
Update changelog.md
bitSmiley Jul 30, 2024
6cc42ed
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 31, 2024
1b9187d
Update zetaclient/chains/bitcoin/observer/inbound.go
bitSmiley Jul 31, 2024
a6bae3d
review feedback
bitSmiley Jul 31, 2024
95e450d
Merge branch 'develop' into feat-parse_inscription
bitSmiley Jul 31, 2024
e07437a
Merge branch 'develop' into feat-parse_inscription
bitSmiley Aug 1, 2024
d3f2344
clean up
bitSmiley Aug 2, 2024
35c70b2
Merge branch 'develop' into feat-parse_inscription
bitSmiley Aug 2, 2024
33f14a9
format code
bitSmiley Aug 2, 2024
90d03b0
Merge branch 'feat-parse_inscription' of bitsmiley-github:bitSmiley/n…
bitSmiley Aug 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
* [2518](https://github.com/zeta-chain/node/pull/2518) - add support for Solana address in zetacore
* [2483](https://github.com/zeta-chain/node/pull/2483) - add priorityFee (gasTipCap) gas to the state
* [2567](https://github.com/zeta-chain/node/pull/2567) - add sign latency metric to zetaclient (zetaclient_sign_latency)
* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envolop parsing
* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envelop parsing
* [2560](https://github.com/zeta-chain/node/pull/2560) - add support for Solana SOL token withdraw
* [2533](https://github.com/zeta-chain/node/pull/2533) - parse memo from both OP_RETURN and inscription

### Refactor

Expand Down
36 changes: 0 additions & 36 deletions zetaclient/chains/bitcoin/observer/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,39 +477,3 @@ func GetBtcEvent(
}
return nil, nil
}

// GetBtcEventWithWitness either returns a valid BTCInboundEvent or nil.
// This method supports data with more than 80 bytes by scanning the witness for possible presence of a tapscript.
// It will first prioritize OP_RETURN over tapscript.
func GetBtcEventWithWitness(
client interfaces.BTCRPCClient,
tx btcjson.TxRawResult,
tssAddress string,
blockNumber uint64,
logger zerolog.Logger,
netParams *chaincfg.Params,
depositorFee float64,
) (*BTCInboundEvent, error) {
// first check for OP_RETURN data
event, err := GetBtcEvent(
client,
tx,
tssAddress,
blockNumber,
logger,
netParams,
depositorFee,
)

if err != nil {
return nil, errors.Wrap(err, "unable to get btc event")
}

if event != nil {
return event, nil
}

// TODO: integrate parsing script

return nil, nil
}
187 changes: 187 additions & 0 deletions zetaclient/chains/bitcoin/observer/witness.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package observer

import (
"encoding/hex"
"fmt"

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/pkg/errors"
"github.com/rs/zerolog"

"github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin"
"github.com/zeta-chain/zetacore/zetaclient/chains/interfaces"
)

// GetBtcEventWithWitness either returns a valid BTCInboundEvent or nil.
// This method supports data with more than 80 bytes by scanning the witness for possible presence of a tapscript.
// It will first prioritize OP_RETURN over tapscript.
func GetBtcEventWithWitness(
client interfaces.BTCRPCClient,
tx btcjson.TxRawResult,
tssAddress string,
blockNumber uint64,
logger zerolog.Logger,
netParams *chaincfg.Params,
depositorFee float64,
) (*BTCInboundEvent, error) {
if len(tx.Vout) < 1 {
logger.Debug().Msgf("no output %s", tx.Txid)
return nil, nil
}
if len(tx.Vin) == 0 {
logger.Debug().Msgf("no input found for inbound: %s", tx.Txid)
return nil, nil
}

if err := isValidRecipient(tx.Vout[0].ScriptPubKey.Hex, tssAddress, netParams); err != nil {
logger.Debug().Msgf("irrelevant recipient %s for tx %s, err: %s", tx.Vout[0].ScriptPubKey.Hex, tx.Txid, err)
return nil, nil
}

isAmountValid, amount := isValidAmount(tx.Vout[0].Value, depositorFee)
if !isAmountValid {
logger.Info().
Msgf("GetBtcEventWithWitness: btc deposit amount %v in txid %s is less than depositor fee %v", tx.Vout[0].Value, tx.Txid, depositorFee)
return nil, nil
}

// Try to extract the memo from the BTC txn. First try to extract from OP_RETURN
// if not found then try to extract from inscription. Return nil if the above two
// cannot find the memo.
var memo []byte
if candidate := tryExtractOpRet(tx, logger); candidate != nil {
memo = candidate
logger.Debug().
Msgf("GetBtcEventWithWitness: found OP_RETURN memo %s in tx %s", hex.EncodeToString(memo), tx.Txid)
} else if candidate = tryExtractInscription(tx, logger); candidate != nil {
memo = candidate
logger.Debug().Msgf("GetBtcEventWithWitness: found inscription memo %s in tx %s", hex.EncodeToString(memo), tx.Txid)
} else {
return nil, errors.Errorf("error getting memo for inbound: %s", tx.Txid)
}

// event found, get sender address
fromAddress, err := GetSenderAddressByVin(client, tx.Vin[0], netParams)
if err != nil {
return nil, errors.Wrapf(err, "error getting sender address for inbound: %s", tx.Txid)
}

return &BTCInboundEvent{
FromAddress: fromAddress,
ToAddress: tssAddress,
Value: amount,
MemoBytes: memo,
BlockNumber: blockNumber,
TxHash: tx.Txid,
}, nil
}

// ParseScriptFromWitness attempts to parse the script from the witness data. Ideally it should be handled by
// bitcoin library, however, it's not found in existing library version. Replace this with actual library implementation
// if libraries are updated.
func ParseScriptFromWitness(witness []string, logger zerolog.Logger) []byte {
length := len(witness)

if length == 0 {
return nil
}

lastElement, err := hex.DecodeString(witness[length-1])
if err != nil {
logger.Debug().Msgf("invalid witness element")
return nil
}

// From BIP341:
// If there are at least two witness elements, and the first byte of
// the last element is 0x50, this last element is called annex a
// and is removed from the witness stack.
if length >= 2 && len(lastElement) > 0 && lastElement[0] == 0x50 {
// account for the extra item removed from the end
witness = witness[:length-1]
}

if len(witness) < 2 {
logger.Debug().Msgf("not script path spending detected, ignore")
return nil
}

// only the script is the focus here, ignore checking control block or whatever else
script, err := hex.DecodeString(witness[len(witness)-2])
if err != nil {
logger.Debug().Msgf("witness script cannot be decoded from hex, ignore")
return nil
}
return script
}

// / Try to extract the memo from the OP_RETURN
func tryExtractOpRet(tx btcjson.TxRawResult, logger zerolog.Logger) []byte {
if len(tx.Vout) < 2 {
logger.Debug().Msgf("txn %s has fewer than 2 outputs, not target OP_RETURN txn", tx.Txid)
return nil
}

memo, found, err := bitcoin.DecodeOpReturnMemo(tx.Vout[1].ScriptPubKey.Hex, tx.Txid)
if err != nil {
logger.Error().Err(err).Msgf("tryExtractOpRet: error decoding OP_RETURN memo: %s", tx.Vout[1].ScriptPubKey.Hex)
return nil
}

if found {
return memo
}
return nil
}

// / Try to extract the memo from inscription
func tryExtractInscription(tx btcjson.TxRawResult, logger zerolog.Logger) []byte {
for i, input := range tx.Vin {
script := ParseScriptFromWitness(input.Witness, logger)
if script == nil {
continue
}

logger.Debug().Msgf("potential witness script, tx %s, input idx %d", tx.Txid, i)

memo, found, err := bitcoin.DecodeScript(script)
if err != nil || !found {
logger.Debug().Msgf("invalid witness script, tx %s, input idx %d", tx.Txid, i)
continue
}

logger.Debug().Msgf("found memo in inscription, tx %s, input idx %d", tx.Txid, i)
return memo
}

return nil
}

func isValidAmount(
incoming float64,
minimal float64,
) (bool, float64) {
if incoming < minimal {
return false, 0
}
return true, incoming - minimal
}

func isValidRecipient(
script string,
tssAddress string,
netParams *chaincfg.Params,
) error {
receiver, err := bitcoin.DecodeScriptP2WPKH(script, netParams)
if err != nil {
return fmt.Errorf("invalid p2wpkh script detected, %s", err)
}

// skip irrelevant tx to us
if receiver != tssAddress {
return fmt.Errorf("irrelevant recipient, %s", receiver)
}

return nil
}
Loading
Loading