feat: add basic tests for runestone

This commit is contained in:
Gaze
2024-04-09 16:46:24 +07:00
parent c010700e65
commit d7b3972169
3 changed files with 297 additions and 21 deletions

View File

@@ -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
}

View File

@@ -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()

View 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,
},
},
},
)
}