gitlab.com/SiaPrime/SiaPrime@v1.4.1/types/transaction_helpers.go (about) 1 package types 2 3 import ( 4 "errors" 5 ) 6 7 // TransactionGraphEdge defines an edge in a TransactionGraph, containing a 8 // source transaction, a destination transaction, a value, and a miner fee. 9 type TransactionGraphEdge struct { 10 Dest int 11 Fee Currency 12 Source int 13 Value Currency 14 } 15 16 // TransactionGraph will return a set of valid transactions that all spend 17 // outputs according to the input graph. Each [source, dest] pair defines an 18 // edge of the graph. The graph must be fully connected and the granparent of 19 // the graph must be the sourceOutput. '0' refers to an edge from the source 20 // output. Each edge also specifies a value for the output, and an amount of 21 // fees. If the fees are zero, no fees will be added for that edge. 'sources' 22 // must be sorted. 23 // 24 // Example of acceptable input: 25 // 26 // sourceOutput: // a valid siacoin output spending to UnlockConditions{}.UnlockHash() 27 // 28 // Sources: [0, 0, 1, 2, 3, 3, 3, 4] 29 // Dests: [1, 2, 3, 3, 4, 4, 5, 6] 30 // 31 // Resulting Graph: 32 // 33 // o 34 // / \ 35 // o o 36 // \ / 37 // o 38 // /|\ 39 // \| \ 40 // o x // 'x' transactions are symbolic, not actually created 41 // | 42 // x 43 // 44 func TransactionGraph(sourceOutput SiacoinOutputID, edges []TransactionGraphEdge) ([]Transaction, error) { 45 // Basic input validation. 46 if len(edges) < 1 { 47 return nil, errors.New("no graph specificed") 48 } 49 50 // Check that the first value of 'sources' is zero, and that the rest of the 51 // array is sorted. 52 if edges[0].Source != 0 { 53 return nil, errors.New("first edge must speficy node 0 as the parent") 54 } 55 if edges[0].Dest != 1 { 56 return nil, errors.New("first edge must speficy node 1 as the child") 57 } 58 latest := edges[0].Source 59 for _, edge := range edges { 60 if edge.Source < latest { 61 return nil, errors.New("'sources' input is not sorted") 62 } 63 latest = edge.Source 64 } 65 66 // Create the set of output ids, and fill out the input ids for the source 67 // transaction. 68 biggest := 0 69 for _, edge := range edges { 70 if edge.Dest > biggest { 71 biggest = edge.Dest 72 } 73 } 74 txnInputs := make([][]SiacoinOutputID, biggest+1) 75 txnInputs[0] = []SiacoinOutputID{sourceOutput} 76 77 // Go through the nodes bit by bit and create outputs. 78 // Fill out the outputs for the source. 79 i, j := 0, 0 80 ts := make([]Transaction, edges[len(edges)-1].Source+1) 81 for i < len(edges) { 82 var t Transaction 83 84 // Grab the inputs for this transaction. 85 for _, outputID := range txnInputs[j] { 86 t.SiacoinInputs = append(t.SiacoinInputs, SiacoinInput{ 87 ParentID: outputID, 88 }) 89 } 90 91 // Grab the outputs for this transaction. 92 startingPoint := i 93 current := edges[i].Source 94 for i < len(edges) && edges[i].Source == current { 95 t.SiacoinOutputs = append(t.SiacoinOutputs, SiacoinOutput{ 96 Value: edges[i].Value, 97 UnlockHash: UnlockConditions{}.UnlockHash(), 98 }) 99 if !edges[i].Fee.IsZero() { 100 t.MinerFees = append(t.MinerFees, edges[i].Fee) 101 } 102 i++ 103 } 104 105 // Record the inputs for the next transactions. 106 for k := startingPoint; k < i; k++ { 107 txnInputs[edges[k].Dest] = append(txnInputs[edges[k].Dest], t.SiacoinOutputID(uint64(k-startingPoint))) 108 } 109 ts[j] = t 110 j++ 111 } 112 113 return ts, nil 114 }