// VulcanizeDB
// Copyright © 2019 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ipld
import (
"bytes"
"encoding/hex"
"fmt"
"strconv"
"github.com/btcsuite/btcd/wire"
"github.com/ipfs/go-cid"
node "github.com/ipfs/go-ipld-format"
mh "github.com/multiformats/go-multihash"
)
type BtcTx struct {
*wire.MsgTx
rawdata []byte
cid cid.Cid
}
// Static (compile time) check that BtcBtcHeader satisfies the node.Node interface.
var _ node.Node = (*BtcTx)(nil)
/*
INPUT
*/
// NewBtcTx converts a *wire.MsgTx into an BtcTx IPLD node
func NewBtcTx(tx *wire.MsgTx) (*BtcTx, error) {
w := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(w); err != nil {
return nil, err
}
rawdata := w.Bytes()
c, err := RawdataToCid(MBitcoinTx, rawdata, mh.DBL_SHA2_256)
if err != nil {
return nil, err
}
return &BtcTx{
MsgTx: tx,
cid: c,
rawdata: rawdata,
}, nil
}
/*
Block INTERFACE
*/
func (t *BtcTx) Cid() cid.Cid {
return t.cid
}
func (t *BtcTx) RawData() []byte {
return t.rawdata
}
func (t *BtcTx) String() string {
return fmt.Sprintf("", t.cid)
}
func (t *BtcTx) Loggable() map[string]interface{} {
return map[string]interface{}{
"type": "bitcoinTx",
}
}
/*
Node INTERFACE
*/
func (t *BtcTx) Links() []*node.Link {
var out []*node.Link
for i, in := range t.MsgTx.TxIn {
lnk := &node.Link{Cid: sha256ToCid(MBitcoinTx, in.PreviousOutPoint.Hash.CloneBytes())}
lnk.Name = fmt.Sprintf("inputs/%d/prevTx", i)
out = append(out, lnk)
}
return out
}
func (t *BtcTx) Resolve(path []string) (interface{}, []string, error) {
switch path[0] {
case "version":
return t.Version, path[1:], nil
case "lockTime":
return t.LockTime, path[1:], nil
case "inputs":
if len(path) == 1 {
return t.MsgTx.TxIn, nil, nil
}
index, err := strconv.Atoi(path[1])
if err != nil {
return nil, nil, err
}
if index >= len(t.MsgTx.TxIn) || index < 0 {
return nil, nil, fmt.Errorf("index out of range")
}
inp := t.MsgTx.TxIn[index]
if len(path) == 2 {
return inp, nil, nil
}
switch path[2] {
case "prevTx":
return &node.Link{Cid: sha256ToCid(MBitcoinTx, inp.PreviousOutPoint.Hash.CloneBytes())}, path[3:], nil
case "seqNo":
return inp.Sequence, path[3:], nil
case "script":
return inp.SignatureScript, path[3:], nil
default:
return nil, nil, fmt.Errorf("no such link")
}
case "outputs":
if len(path) == 1 {
return t.TxOut, nil, nil
}
index, err := strconv.Atoi(path[1])
if err != nil {
return nil, nil, err
}
if index >= len(t.TxOut) || index < 0 {
return nil, nil, fmt.Errorf("index out of range")
}
outp := t.TxOut[index]
if len(path) == 2 {
return outp, path[2:], nil
}
switch path[2] {
case "value":
return outp.Value, path[3:], nil
case "script":
/*
if outp.Script[0] == 0x6a { // OP_RETURN
c, err := cid.Decode(string(outp.Script[1:]))
if err == nil {
return &node.Link{Cid: c}, path[3:], nil
}
}
*/
return outp.PkScript, path[3:], nil
default:
return nil, nil, fmt.Errorf("no such link")
}
default:
return nil, nil, fmt.Errorf("no such link")
}
}
func (t *BtcTx) ResolveLink(path []string) (*node.Link, []string, error) {
i, rest, err := t.Resolve(path)
if err != nil {
return nil, rest, err
}
lnk, ok := i.(*node.Link)
if !ok {
return nil, nil, fmt.Errorf("value was not a link")
}
return lnk, rest, nil
}
func (t *BtcTx) Size() (uint64, error) {
return uint64(len(t.RawData())), nil
}
func (t *BtcTx) Stat() (*node.NodeStat, error) {
return &node.NodeStat{}, nil
}
func (t *BtcTx) Copy() node.Node {
nt := *t // cheating shallow copy
return &nt
}
func (t *BtcTx) Tree(p string, depth int) []string {
if depth == 0 {
return nil
}
switch p {
case "inputs":
return t.treeInputs(nil, depth+1)
case "outputs":
return t.treeOutputs(nil, depth+1)
case "":
out := []string{"version", "timeLock", "inputs", "outputs"}
out = t.treeInputs(out, depth)
out = t.treeOutputs(out, depth)
return out
default:
return nil
}
}
func (t *BtcTx) treeInputs(out []string, depth int) []string {
if depth < 2 {
return out
}
for i := range t.TxIn {
inp := "inputs/" + fmt.Sprint(i)
out = append(out, inp)
if depth > 2 {
out = append(out, inp+"/prevTx", inp+"/seqNo", inp+"/script")
}
}
return out
}
func (t *BtcTx) treeOutputs(out []string, depth int) []string {
if depth < 2 {
return out
}
for i := range t.TxOut {
o := "outputs/" + fmt.Sprint(i)
out = append(out, o)
if depth > 2 {
out = append(out, o+"/script", o+"/value")
}
}
return out
}
func (t *BtcTx) BTCSha() []byte {
mh, _ := mh.Sum(t.RawData(), mh.DBL_SHA2_256, -1)
return []byte(mh[2:])
}
func (t *BtcTx) HexHash() string {
return hex.EncodeToString(revString(t.BTCSha()))
}