mirror of
https://github.com/alexgo-io/gaze-brc20-indexer.git
synced 2026-01-12 22:22:19 +08:00
feat: implement envelope tests
This commit is contained in:
@@ -38,7 +38,7 @@ func ParseEnvelopesFromTx(tx *wire.MsgTx) []*Envelope {
|
||||
return envelopes
|
||||
}
|
||||
|
||||
const protocolId = "ord"
|
||||
var protocolId = []byte("ord")
|
||||
|
||||
func envelopesFromTapScript(tokenizer txscript.ScriptTokenizer, inputIndex int) []*Envelope {
|
||||
envelopes := make([]*Envelope, 0)
|
||||
@@ -71,7 +71,7 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
|
||||
}
|
||||
|
||||
tokenizer.Next()
|
||||
if !bytes.Equal(tokenizer.Data(), []byte(protocolId)) {
|
||||
if !bytes.Equal(tokenizer.Data(), protocolId) {
|
||||
return nil, tokenizer.Opcode() == txscript.OP_FALSE
|
||||
}
|
||||
|
||||
@@ -149,19 +149,25 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
|
||||
}
|
||||
}
|
||||
// incomplete envelope
|
||||
if tokenizer.Done() {
|
||||
if tokenizer.Done() && tokenizer.Opcode() != txscript.OP_ENDIF {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
_, bodyIndex, ok := lo.FindIndexOf(payload, func(value []byte) bool {
|
||||
return len(value) == 0
|
||||
})
|
||||
var fieldPayloads, bodyPayloads [][]byte
|
||||
if ok {
|
||||
fieldPayloads = payload[:]
|
||||
} else {
|
||||
// find body (empty data push in even index payload)
|
||||
bodyIndex := -1
|
||||
for i, value := range payload {
|
||||
if i%2 == 0 && len(value) == 0 {
|
||||
bodyIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
var fieldPayloads [][]byte
|
||||
var body []byte
|
||||
if bodyIndex != -1 {
|
||||
fieldPayloads = payload[:bodyIndex]
|
||||
bodyPayloads = payload[bodyIndex+1:]
|
||||
body = lo.Flatten(payload[bodyIndex+1:])
|
||||
} else {
|
||||
fieldPayloads = payload[:]
|
||||
}
|
||||
|
||||
var incompleteField bool
|
||||
@@ -203,8 +209,6 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
|
||||
return key%2 == 0
|
||||
})
|
||||
|
||||
body := lo.Flatten(bodyPayloads)
|
||||
|
||||
var delegate, parent *InscriptionId
|
||||
inscriptionId, err := NewInscriptionIdFromString(string(rawDelegate))
|
||||
if err == nil {
|
||||
@@ -216,10 +220,14 @@ func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, o
|
||||
}
|
||||
|
||||
var pointer *uint64
|
||||
// if rawPointer fits in uint64
|
||||
if len(rawPointer) <= 8 || lo.EveryBy(rawPointer[8:], func(value byte) bool {
|
||||
// if rawPointer is not nil and fits in uint64
|
||||
if rawPointer != nil && (len(rawPointer) <= 8 || lo.EveryBy(rawPointer[8:], func(value byte) bool {
|
||||
return value != 0
|
||||
}) {
|
||||
})) {
|
||||
// pad zero bytes to 8 bytes
|
||||
if len(rawPointer) < 8 {
|
||||
rawPointer = append(rawPointer, make([]byte, 8-len(rawPointer))...)
|
||||
}
|
||||
pointer = lo.ToPtr(binary.LittleEndian.Uint64(rawPointer))
|
||||
}
|
||||
|
||||
@@ -252,6 +260,10 @@ func (fields Fields) Take(tag Tag) []byte {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if tag.IsChunked() {
|
||||
delete(fields, tag)
|
||||
return lo.Flatten(values)
|
||||
} else {
|
||||
first := values[0]
|
||||
values = values[1:]
|
||||
if len(values) == 0 {
|
||||
@@ -260,11 +272,7 @@ func (fields Fields) Take(tag Tag) []byte {
|
||||
fields[tag] = values
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
func isDataPushOpCode(opCode byte) bool {
|
||||
// includes OP_0, OP_DATA_1 to OP_DATA_75, OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4
|
||||
return opCode <= txscript.OP_PUSHDATA4
|
||||
}
|
||||
}
|
||||
|
||||
func extractTapScript(witness [][]byte) (txscript.ScriptTokenizer, bool) {
|
||||
|
||||
741
modules/brc20/internal/ordinals/envelope_test.go
Normal file
741
modules/brc20/internal/ordinals/envelope_test.go
Normal file
@@ -0,0 +1,741 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseEnvelopesFromTx(t *testing.T) {
|
||||
testTx := func(t *testing.T, tx *wire.MsgTx, expected []*Envelope) {
|
||||
t.Helper()
|
||||
|
||||
envelopes := ParseEnvelopesFromTx(tx)
|
||||
assert.Equal(t, expected, envelopes)
|
||||
}
|
||||
testParseWitness := func(t *testing.T, tapScript []byte, expected []*Envelope) {
|
||||
t.Helper()
|
||||
|
||||
tx := &wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
Witness: wire.TxWitness{
|
||||
tapScript,
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testTx(t, tx, expected)
|
||||
}
|
||||
testEnvelope := func(t *testing.T, payload [][]byte, expected []*Envelope) {
|
||||
t.Helper()
|
||||
|
||||
builder := NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF)
|
||||
for _, data := range payload {
|
||||
builder.AddData(data)
|
||||
}
|
||||
builder.AddOp(txscript.OP_ENDIF)
|
||||
script, err := builder.Script()
|
||||
assert.NoError(t, err)
|
||||
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
|
||||
t.Run("empty_witness", func(t *testing.T) {
|
||||
testTx(t, &wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{{
|
||||
Witness: wire.TxWitness{},
|
||||
}},
|
||||
}, []*Envelope{})
|
||||
})
|
||||
t.Run("ignore_key_path_spends", func(t *testing.T) {
|
||||
testTx(t, &wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{{
|
||||
Witness: wire.TxWitness{
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
},
|
||||
}},
|
||||
}, []*Envelope{})
|
||||
})
|
||||
t.Run("ignore_key_path_spends_with_annex", func(t *testing.T) {
|
||||
testTx(t, &wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{{
|
||||
Witness: wire.TxWitness{
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]byte{txscript.TaprootAnnexTag},
|
||||
},
|
||||
}},
|
||||
}, []*Envelope{})
|
||||
})
|
||||
t.Run("parse_from_tapscript", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{{}},
|
||||
)
|
||||
})
|
||||
t.Run("ignore_unparsable_scripts", func(t *testing.T) {
|
||||
script := utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
|
||||
script = append(script, 0x01)
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_inscription", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
Script()),
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("duplicate_field", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagNop.Bytes(),
|
||||
{},
|
||||
TagNop.Bytes(),
|
||||
{},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
DuplicateField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("with_content_type", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("ord"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("with_content_encoding", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagContentEncoding.Bytes(),
|
||||
[]byte("br"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("ord"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
ContentEncoding: "br",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("with_unknown_tag", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagNop.Bytes(),
|
||||
[]byte("bar"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("ord"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_body", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_content_type", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagBody.Bytes(),
|
||||
[]byte("foo"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("foo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_body_in_multiple_pushes", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("foo"),
|
||||
[]byte("bar"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("foobar"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_body_in_zero_pushes", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte(""),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_body_in_multiple_empty_pushes", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte(""),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_ignore_trailing", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("ord")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
AddOp(txscript.OP_CHECKSIG).
|
||||
Script()),
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_ignore_preceding", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_CHECKSIG).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("ord")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("multiple_inscriptions_in_a_single_witness", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("foo")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("bar")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("foo"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("bar"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
Offset: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("invalid_utf8_does_not_render_inscription_invalid", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
{0b10000000},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte{0b10000000},
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_endif", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
Script()),
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("no_op_false", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("empty_envelope", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{},
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("wrong_protocol_identifier", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
[]byte("foo"),
|
||||
},
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("extract_from_second_input", func(t *testing.T) {
|
||||
testTx(
|
||||
t,
|
||||
&wire.MsgTx{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*wire.TxIn{{}, {
|
||||
Witness: wire.TxWitness{
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("ord")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script(),
|
||||
),
|
||||
{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
InputIndex: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("inscribe_png", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("image/png"),
|
||||
TagBody.Bytes(),
|
||||
{0x01, 0x02, 0x03},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte{0x01, 0x02, 0x03},
|
||||
ContentType: "image/png",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("unknown_odd_fields", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagNop.Bytes(),
|
||||
{0x00},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("unknown_even_fields", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagUnbound.Bytes(),
|
||||
{0x00},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
UnrecognizedEvenField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("pointer_field_is_recognized", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagPointer.Bytes(),
|
||||
{0x01},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Pointer: lo.ToPtr(uint64(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("duplicate_pointer_field_makes_inscription_unbound", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagPointer.Bytes(),
|
||||
{0x01},
|
||||
TagPointer.Bytes(),
|
||||
{0x00},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Pointer: lo.ToPtr(uint64(1)),
|
||||
},
|
||||
DuplicateField: true,
|
||||
UnrecognizedEvenField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("incomplete_field", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagNop.Bytes(),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
IncompleteField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("metadata_is_parsed_correctly", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagMetadata.Bytes(),
|
||||
{},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Metadata: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("metadata_is_parsed_correctly_from_chunks", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagMetadata.Bytes(),
|
||||
{0x00},
|
||||
TagMetadata.Bytes(),
|
||||
{0x01},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Metadata: []byte{0x00, 0x01},
|
||||
},
|
||||
DuplicateField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("pushnum_opcodes_are_parsed_correctly", func(t *testing.T) {
|
||||
pushNumOpCodes := map[byte][]byte{
|
||||
txscript.OP_1NEGATE: {0x81},
|
||||
txscript.OP_1: {0x01},
|
||||
txscript.OP_2: {0x02},
|
||||
txscript.OP_3: {0x03},
|
||||
txscript.OP_4: {0x04},
|
||||
txscript.OP_5: {0x05},
|
||||
txscript.OP_6: {0x06},
|
||||
txscript.OP_7: {0x07},
|
||||
txscript.OP_8: {0x08},
|
||||
txscript.OP_9: {0x09},
|
||||
txscript.OP_10: {0x10},
|
||||
txscript.OP_11: {0x11},
|
||||
txscript.OP_12: {0x12},
|
||||
txscript.OP_13: {0x13},
|
||||
txscript.OP_14: {0x14},
|
||||
txscript.OP_15: {0x15},
|
||||
txscript.OP_16: {0x16},
|
||||
}
|
||||
for opCode, value := range pushNumOpCodes {
|
||||
script := utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddOp(opCode).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: value,
|
||||
},
|
||||
PushNum: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
t.Run("stuttering", func(t *testing.T) {
|
||||
script := utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
Stutter: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
script = utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
Stutter: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
script = utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_AND).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
Stutter: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
170
modules/brc20/internal/ordinals/script_builder.go
Normal file
170
modules/brc20/internal/ordinals/script_builder.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
)
|
||||
|
||||
// PushScriptBuilder is a helper to build scripts that requires data pushes to use OP_PUSHDATA* or OP_DATA_* opcodes only.
|
||||
// Empty data pushes are still encoded as OP_0.
|
||||
type PushScriptBuilder struct {
|
||||
script []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func NewPushScriptBuilder() *PushScriptBuilder {
|
||||
return &PushScriptBuilder{}
|
||||
}
|
||||
|
||||
// canonicalDataSize returns the number of bytes the canonical encoding of the
|
||||
// data will take.
|
||||
func canonicalDataSize(data []byte) int {
|
||||
dataLen := len(data)
|
||||
|
||||
// When the data consists of a single number that can be represented
|
||||
// by one of the "small integer" opcodes, that opcode will be instead
|
||||
// of a data push opcode followed by the number.
|
||||
if dataLen == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if dataLen < txscript.OP_PUSHDATA1 {
|
||||
return 1 + dataLen
|
||||
} else if dataLen <= 0xff {
|
||||
return 2 + dataLen
|
||||
} else if dataLen <= 0xffff {
|
||||
return 3 + dataLen
|
||||
}
|
||||
|
||||
return 5 + dataLen
|
||||
}
|
||||
|
||||
func pushDataToBytes(data []byte) []byte {
|
||||
if len(data) == 0 {
|
||||
return []byte{txscript.OP_0}
|
||||
}
|
||||
script := make([]byte, 0)
|
||||
dataLen := len(data)
|
||||
if dataLen < txscript.OP_PUSHDATA1 {
|
||||
script = append(script, byte(txscript.OP_DATA_1-1+dataLen))
|
||||
} else if dataLen <= 0xff {
|
||||
script = append(script, txscript.OP_PUSHDATA1, byte(dataLen))
|
||||
} else if dataLen <= 0xffff {
|
||||
buf := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(buf, uint16(dataLen))
|
||||
script = append(script, txscript.OP_PUSHDATA2)
|
||||
script = append(script, buf...)
|
||||
} else {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, uint32(dataLen))
|
||||
script = append(script, txscript.OP_PUSHDATA4)
|
||||
script = append(script, buf...)
|
||||
}
|
||||
// Append the actual data.
|
||||
script = append(script, data...)
|
||||
return script
|
||||
}
|
||||
|
||||
// AddData pushes the passed data to the end of the script. It automatically
|
||||
// chooses canonical opcodes depending on the length of the data. A zero length
|
||||
// buffer will lead to a push of empty data onto the stack (OP_0) and any push
|
||||
// of data greater than MaxScriptElementSize will not modify the script since
|
||||
// that is not allowed by the script engine. Also, the script will not be
|
||||
// modified if pushing the data would cause the script to exceed the maximum
|
||||
// allowed script engine size.
|
||||
func (b *PushScriptBuilder) AddData(data []byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
// Pushes that would cause the script to exceed the largest allowed
|
||||
// script size would result in a non-canonical script.
|
||||
dataSize := canonicalDataSize(data)
|
||||
if len(b.script)+dataSize > txscript.MaxScriptSize {
|
||||
str := fmt.Sprintf("adding %d bytes of data would exceed the "+
|
||||
"maximum allowed canonical script length of %d",
|
||||
dataSize, txscript.MaxScriptSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
// Pushes larger than the max script element size would result in a
|
||||
// script that is not canonical.
|
||||
dataLen := len(data)
|
||||
if dataLen > txscript.MaxScriptElementSize {
|
||||
str := fmt.Sprintf("adding a data element of %d bytes would "+
|
||||
"exceed the maximum allowed script element size of %d",
|
||||
dataLen, txscript.MaxScriptElementSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, pushDataToBytes(data)...)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddFullData should not typically be used by ordinary users as it does not
|
||||
// include the checks which prevent data pushes larger than the maximum allowed
|
||||
// sizes which leads to scripts that can't be executed. This is provided for
|
||||
// testing purposes such as regression tests where sizes are intentionally made
|
||||
// larger than allowed.
|
||||
//
|
||||
// Use AddData instead.
|
||||
func (b *PushScriptBuilder) AddFullData(data []byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, pushDataToBytes(data)...)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddOp pushes the passed opcode to the end of the script. The script will not
|
||||
// be modified if pushing the opcode would cause the script to exceed the
|
||||
// maximum allowed script engine size.
|
||||
func (b *PushScriptBuilder) AddOp(opcode byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
// Pushes that would cause the script to exceed the largest allowed
|
||||
// script size would result in a non-canonical script.
|
||||
if len(b.script)+1 > txscript.MaxScriptSize {
|
||||
str := fmt.Sprintf("adding an opcode would exceed the maximum "+
|
||||
"allowed canonical script length of %d", txscript.MaxScriptSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, opcode)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddOps pushes the passed opcodes to the end of the script. The script will
|
||||
// not be modified if pushing the opcodes would cause the script to exceed the
|
||||
// maximum allowed script engine size.
|
||||
func (b *PushScriptBuilder) AddOps(opcodes []byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
// Pushes that would cause the script to exceed the largest allowed
|
||||
// script size would result in a non-canonical script.
|
||||
if len(b.script)+len(opcodes) > txscript.MaxScriptSize {
|
||||
str := fmt.Sprintf("adding opcodes would exceed the maximum "+
|
||||
"allowed canonical script length of %d", txscript.MaxScriptSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, opcodes...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Script returns the currently built script. When any errors occurred while
|
||||
// building the script, the script will be returned up the point of the first
|
||||
// error along with the error.
|
||||
func (b *PushScriptBuilder) Script() ([]byte, error) {
|
||||
return b.script, b.err
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package ordinals
|
||||
type Tag uint8
|
||||
|
||||
var (
|
||||
TagBody = Tag(0)
|
||||
TagPointer = Tag(2)
|
||||
// TagUnbound is unrecognized
|
||||
TagUnbound = Tag(66)
|
||||
@@ -34,6 +35,22 @@ func (t Tag) IsValid() bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
var chunkedTags = map[Tag]struct{}{
|
||||
TagMetadata: {},
|
||||
}
|
||||
|
||||
func (t Tag) IsChunked() bool {
|
||||
_, ok := chunkedTags[t]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (t Tag) Bytes() []byte {
|
||||
if t == TagBody {
|
||||
return []byte{} // body tag is empty data push
|
||||
}
|
||||
return []byte{byte(t)}
|
||||
}
|
||||
|
||||
func ParseTag(input interface{}) (Tag, error) {
|
||||
switch input := input.(type) {
|
||||
case Tag:
|
||||
|
||||
Reference in New Issue
Block a user