transaction_helpers.go 3.07 KB
Newer Older
1 2 3 4 5 6
package types

import (
	"errors"
)

7 8 9 10 11 12 13 14 15
// TransactionGraphEdge defines an edge in a TransactionGraph, containing a
// source transaction, a destination transaction, a value, and a miner fee.
type TransactionGraphEdge struct {
	Dest   int
	Fee    Currency
	Source int
	Value  Currency
}

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
// TransactionGraph will return a set of valid transactions that all spend
// outputs according to the input graph. Each [source, dest] pair defines an
// edge of the graph. The graph must be fully connected and the granparent of
// the graph must be the sourceOutput. '0' refers to an edge from the source
// output. Each edge also specifies a value for the output, and an amount of
// fees. If the fees are zero, no fees will be added for that edge. 'sources'
// must be sorted.
//
// Example of acceptable input:
//
// sourceOutput: // a valid siacoin output spending to UnlockConditions{}.UnlockHash()
//
// Sources: [0, 0, 1, 2, 3, 3, 3, 4]
// Dests:   [1, 2, 3, 3, 4, 4, 5, 6]
//
// Resulting Graph:
//
//    o
//   / \
//  o   o
//   \ /
//    o
//   /|\
//   \| \
//    o  x // 'x' transactions are symbolic, not actually created
//    |
//    x
//
44
func TransactionGraph(sourceOutput SiacoinOutputID, edges []TransactionGraphEdge) ([]Transaction, error) {
45
	// Basic input validation.
46
	if len(edges) < 1 {
47 48 49 50 51
		return nil, errors.New("no graph specificed")
	}

	// Check that the first value of 'sources' is zero, and that the rest of the
	// array is sorted.
52
	if edges[0].Source != 0 {
53 54
		return nil, errors.New("first edge must speficy node 0 as the parent")
	}
55
	if edges[0].Dest != 1 {
56 57
		return nil, errors.New("first edge must speficy node 1 as the child")
	}
58 59 60
	latest := edges[0].Source
	for _, edge := range edges {
		if edge.Source < latest {
61 62
			return nil, errors.New("'sources' input is not sorted")
		}
63
		latest = edge.Source
64 65 66 67 68
	}

	// Create the set of output ids, and fill out the input ids for the source
	// transaction.
	biggest := 0
69 70 71
	for _, edge := range edges {
		if edge.Dest > biggest {
			biggest = edge.Dest
72 73 74 75 76 77 78
		}
	}
	txnInputs := make([][]SiacoinOutputID, biggest+1)
	txnInputs[0] = []SiacoinOutputID{sourceOutput}

	// Go through the nodes bit by bit and create outputs.
	// Fill out the outputs for the source.
79
	i, j := 0, 0
80 81
	ts := make([]Transaction, edges[len(edges)-1].Source+1)
	for i < len(edges) {
82 83 84 85 86 87 88 89 90 91 92
		var t Transaction

		// Grab the inputs for this transaction.
		for _, outputID := range txnInputs[j] {
			t.SiacoinInputs = append(t.SiacoinInputs, SiacoinInput{
				ParentID: outputID,
			})
		}

		// Grab the outputs for this transaction.
		startingPoint := i
93 94
		current := edges[i].Source
		for i < len(edges) && edges[i].Source == current {
95
			t.SiacoinOutputs = append(t.SiacoinOutputs, SiacoinOutput{
96
				Value:      edges[i].Value,
97 98
				UnlockHash: UnlockConditions{}.UnlockHash(),
			})
99 100
			if !edges[i].Fee.IsZero() {
				t.MinerFees = append(t.MinerFees, edges[i].Fee)
101 102 103 104 105 106
			}
			i++
		}

		// Record the inputs for the next transactions.
		for k := startingPoint; k < i; k++ {
107
			txnInputs[edges[k].Dest] = append(txnInputs[edges[k].Dest], t.SiacoinOutputID(uint64(k-startingPoint)))
108 109 110 111 112 113 114
		}
		ts[j] = t
		j++
	}

	return ts, nil
}