mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-04-29 12:15:13 +08:00
feat: add basic tests for runestone
This commit is contained in:
@@ -33,7 +33,7 @@ func (fields Fields) Take(tag Tag) *uint128.Uint128 {
|
||||
|
||||
func MessageFromIntegers(tx *wire.MsgTx, payload []uint128.Uint128) Message {
|
||||
flaws := Flaws(0)
|
||||
edicts := make([]Edict, 0)
|
||||
var edicts []Edict
|
||||
fields := make(map[Tag][]uint128.Uint128)
|
||||
|
||||
for i := 0; i < len(payload); i += 2 {
|
||||
@@ -59,7 +59,7 @@ func MessageFromIntegers(tx *wire.MsgTx, payload []uint128.Uint128) Message {
|
||||
flaws |= FlawFlagEdictOutput.Mask()
|
||||
break
|
||||
}
|
||||
nextRuneId, err := runeId.Next(blockDelta.Uint64(), txIndexDelta.Uint32()) // safe to cast as uint32 because we checked
|
||||
runeId, err = runeId.Next(blockDelta.Uint64(), txIndexDelta.Uint32()) // safe to cast as uint32 because we checked
|
||||
if err != nil {
|
||||
flaws |= FlawFlagEdictRuneId.Mask()
|
||||
break
|
||||
@@ -70,7 +70,6 @@ func MessageFromIntegers(tx *wire.MsgTx, payload []uint128.Uint128) Message {
|
||||
Output: int(output.Uint64()),
|
||||
}
|
||||
edicts = append(edicts, edict)
|
||||
runeId = nextRuneId
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package runes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"slices"
|
||||
|
||||
@@ -156,6 +158,7 @@ func DecipherRunestone(tx *wire.MsgTx) (*Runestone, error) {
|
||||
|
||||
integers, err := decodeLEB128VarIntsFromPayload(payload)
|
||||
if err != nil {
|
||||
log.Printf("warning: %v\n", err)
|
||||
flaws |= FlawFlagVarInt.Mask()
|
||||
return &Runestone{
|
||||
Cenotaph: true,
|
||||
@@ -172,17 +175,17 @@ func DecipherRunestone(tx *wire.MsgTx) (*Runestone, error) {
|
||||
|
||||
var etching *Etching
|
||||
if flags.Take(FlagEtching) {
|
||||
divisibility := fields.Take(TagDivisibility)
|
||||
if divisibility != nil && divisibility.Cmp64(uint64(maxDivisibility)) > 0 {
|
||||
divisibility = nil
|
||||
divisibilityU128 := fields.Take(TagDivisibility)
|
||||
if divisibilityU128 != nil && divisibilityU128.Cmp64(uint64(maxDivisibility)) > 0 {
|
||||
divisibilityU128 = nil
|
||||
}
|
||||
spacers := fields.Take(TagSpacers)
|
||||
if spacers != nil && spacers.Cmp64(uint64(maxSpacers)) > 0 {
|
||||
spacers = nil
|
||||
spacersU128 := fields.Take(TagSpacers)
|
||||
if spacersU128 != nil && spacersU128.Cmp64(uint64(maxSpacers)) > 0 {
|
||||
spacersU128 = nil
|
||||
}
|
||||
symbol := fields.Take(TagSymbol)
|
||||
if symbol != nil && symbol.Cmp64(math.MaxInt32) > 0 {
|
||||
symbol = nil
|
||||
symbolU128 := fields.Take(TagSymbol)
|
||||
if symbolU128 != nil && symbolU128.Cmp64(math.MaxInt32) > 0 {
|
||||
symbolU128 = nil
|
||||
}
|
||||
|
||||
var terms *Terms
|
||||
@@ -210,12 +213,25 @@ func DecipherRunestone(tx *wire.MsgTx) (*Runestone, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var divisibility *uint8
|
||||
if divisibilityU128 != nil {
|
||||
divisibility = lo.ToPtr(divisibilityU128.Uint8())
|
||||
}
|
||||
var spacers *uint32
|
||||
if spacersU128 != nil {
|
||||
spacers = lo.ToPtr(spacersU128.Uint32())
|
||||
}
|
||||
var symbol *rune
|
||||
if symbolU128 != nil {
|
||||
symbol = lo.ToPtr(rune(symbolU128.Uint32()))
|
||||
}
|
||||
|
||||
etching = &Etching{
|
||||
Divisibility: lo.ToPtr(divisibility.Uint8()),
|
||||
Divisibility: divisibility,
|
||||
Premine: fields.Take(TagPremine),
|
||||
Rune: (*Rune)(fields.Take(TagRune)),
|
||||
Spacers: lo.ToPtr(spacers.Uint32()),
|
||||
Symbol: lo.ToPtr(rune(symbol.Uint32())),
|
||||
Spacers: spacers,
|
||||
Symbol: symbol,
|
||||
Terms: terms,
|
||||
}
|
||||
}
|
||||
@@ -238,12 +254,14 @@ func DecipherRunestone(tx *wire.MsgTx) (*Runestone, error) {
|
||||
pointer = lo.ToPtr(pointerU128.Uint64())
|
||||
}
|
||||
|
||||
_, err = etching.Supply()
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.OverflowUint128) {
|
||||
flaws |= FlawFlagSupplyOverflow.Mask()
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "cannot calculate supply")
|
||||
if etching != nil {
|
||||
_, err = etching.Supply()
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.OverflowUint128) {
|
||||
flaws |= FlawFlagSupplyOverflow.Mask()
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "cannot calculate supply")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,19 +297,31 @@ func runestonePayloadFromTx(tx *wire.MsgTx) ([]byte, Flaws) {
|
||||
|
||||
// payload must start with OP_RETURN
|
||||
tokenizer.Next()
|
||||
if err := tokenizer.Err(); err != nil {
|
||||
continue
|
||||
}
|
||||
if opCode := tokenizer.Opcode(); opCode != txscript.OP_RETURN {
|
||||
continue
|
||||
}
|
||||
|
||||
// next opcode must be the magic number
|
||||
tokenizer.Next()
|
||||
if err := tokenizer.Err(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
if opCode := tokenizer.Opcode(); opCode != RUNESTONE_PAYLOAD_MAGIC_NUMBER {
|
||||
continue
|
||||
}
|
||||
|
||||
// this output is now selected to be the runestone output. Any errors from now on will be considered a flaw.
|
||||
|
||||
// construct the payload by concatenating the remaining data pushes
|
||||
payload := make([]byte, 0)
|
||||
for tokenizer.Next() {
|
||||
if tokenizer.Err() != nil {
|
||||
return nil, FlawFlagInvalidScript.Mask()
|
||||
}
|
||||
data := tokenizer.Data()
|
||||
if data == nil {
|
||||
return nil, FlawFlagOpCode.Mask()
|
||||
|
||||
247
modules/runes/runestone_test.go
Normal file
247
modules/runes/runestone_test.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package runes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gaze-network/indexer-network/lib/leb128"
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func encodeLEB128VarIntsToPayload(integers []uint128.Uint128) []byte {
|
||||
payload := make([]byte, 0)
|
||||
for _, integer := range integers {
|
||||
payload = append(payload, leb128.EncodeLEB128(integer)...)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func TestDecipherRunestone(t *testing.T) {
|
||||
testDecipherTx := func(name string, tx *wire.MsgTx, expected *Runestone) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runestone, err := DecipherRunestone(tx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, runestone)
|
||||
})
|
||||
}
|
||||
|
||||
testDecipherInteger := func(name string, integers []uint128.Uint128, expected *Runestone) {
|
||||
payload := encodeLEB128VarIntsToPayload(integers)
|
||||
pkScript, err := txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddData(payload).
|
||||
Script()
|
||||
assert.NoError(t, err)
|
||||
tx := &wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
PkScript: pkScript,
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
testDecipherTx(name, tx, expected)
|
||||
}
|
||||
|
||||
testDecipherPkScript := func(name string, pkScript []byte, expected *Runestone) {
|
||||
tx := &wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
PkScript: pkScript,
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
testDecipherTx(name, tx, expected)
|
||||
}
|
||||
|
||||
testDecipherPkScript(
|
||||
"decipher_returns_none_if_first_opcode_is_malformed",
|
||||
utils.Must(txscript.NewScriptBuilder().AddOp(txscript.OP_DATA_4).Script()),
|
||||
nil,
|
||||
)
|
||||
testDecipherTx(
|
||||
"deciphering_transaction_with_non_op_return_output_returns_none",
|
||||
&wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{},
|
||||
TxOut: []*wire.TxOut{},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"deciphering_transaction_with_bare_op_return_returns_none",
|
||||
utils.Must(txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).Script()),
|
||||
nil,
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"deciphering_transaction_with_non_matching_op_return_returns_none",
|
||||
utils.Must(txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).AddOp(txscript.OP_1).Script()),
|
||||
nil,
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"deciphering_valid_runestone_with_invalid_script_postfix_returns_invalid_payload",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddOp(txscript.OP_DATA_4).
|
||||
Script()),
|
||||
&Runestone{
|
||||
Cenotaph: true,
|
||||
Flaws: FlawFlagInvalidScript.Mask(),
|
||||
},
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"deciphering_runestone_with_truncated_varint_is_cenotaph",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddData([]byte{128}).
|
||||
Script()),
|
||||
&Runestone{
|
||||
Cenotaph: true,
|
||||
Flaws: FlawFlagVarInt.Mask(),
|
||||
},
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"outputs_with_non_pushdata_opcodes_are_cenotaph_1",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddOp(txscript.OP_VERIFY).
|
||||
AddData([]byte{0}).
|
||||
AddData(leb128.EncodeLEB128(uint128.From64(1))).
|
||||
AddData(leb128.EncodeLEB128(uint128.From64(1))).
|
||||
AddData([]byte{2, 0}).
|
||||
Script()),
|
||||
&Runestone{
|
||||
Cenotaph: true,
|
||||
Flaws: FlawFlagOpCode.Mask(),
|
||||
},
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"outputs_with_non_pushdata_opcodes_are_cenotaph_2",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddData([]byte{0}).
|
||||
AddData(leb128.EncodeLEB128(uint128.From64(1))).
|
||||
AddData(leb128.EncodeLEB128(uint128.From64(2))).
|
||||
AddData([]byte{3, 0}).
|
||||
Script()),
|
||||
&Runestone{
|
||||
Cenotaph: true,
|
||||
Flaws: FlawFlagOpCode.Mask(),
|
||||
},
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"pushnum_opcodes_in_runestone_produce_cenotaph",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddOp(txscript.OP_1).
|
||||
Script()),
|
||||
&Runestone{
|
||||
Cenotaph: true,
|
||||
Flaws: FlawFlagOpCode.Mask(),
|
||||
},
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"deciphering_empty_runestone_is_successful",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
Script()),
|
||||
&Runestone{},
|
||||
)
|
||||
testDecipherPkScript(
|
||||
"invalid_input_scripts_are_skipped_when_searching_for_runestone",
|
||||
utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
Script()),
|
||||
&Runestone{},
|
||||
)
|
||||
testDecipherTx(
|
||||
"invalid_input_scripts_are_skipped_when_searching_for_runestone",
|
||||
&wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
PkScript: utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(txscript.OP_DATA_9).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddOp(txscript.OP_DATA_4).
|
||||
Script()),
|
||||
},
|
||||
{
|
||||
PkScript: utils.Must(txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_RETURN).
|
||||
AddOp(RUNESTONE_PAYLOAD_MAGIC_NUMBER).
|
||||
AddData(encodeLEB128VarIntsToPayload([]uint128.Uint128{
|
||||
TagMint.Uint128(),
|
||||
uint128.From64(1),
|
||||
TagMint.Uint128(),
|
||||
uint128.From64(1),
|
||||
})).
|
||||
Script()),
|
||||
},
|
||||
},
|
||||
},
|
||||
&Runestone{
|
||||
Mint: lo.ToPtr(RuneId{1, 1}),
|
||||
},
|
||||
)
|
||||
|
||||
testDecipherInteger(
|
||||
"deciphering_non_empty_runestone_is_successful",
|
||||
[]uint128.Uint128{TagBody.Uint128(), uint128.From64(1), uint128.From64(1), uint128.From64(2), uint128.From64(0)},
|
||||
&Runestone{
|
||||
Edicts: []Edict{
|
||||
{
|
||||
Id: RuneId{1, 1},
|
||||
Amount: uint128.From64(2),
|
||||
Output: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
testDecipherInteger(
|
||||
"decipher_etching",
|
||||
[]uint128.Uint128{
|
||||
TagFlags.Uint128(),
|
||||
FlagEtching.Mask().Uint128(),
|
||||
TagBody.Uint128(),
|
||||
uint128.From64(1),
|
||||
uint128.From64(1),
|
||||
uint128.From64(2),
|
||||
uint128.From64(0),
|
||||
},
|
||||
&Runestone{
|
||||
Etching: &Etching{},
|
||||
Edicts: []Edict{
|
||||
{
|
||||
Id: RuneId{1, 1},
|
||||
Amount: uint128.From64(2),
|
||||
Output: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user