package main import ( "bytes" "crypto/sha256" "encoding/gob" "encoding/hex" "fmt" "os" ) const subsidy = 10 // Transaction represents a blockchain transaction type Transaction struct { ID []byte Vin []TXInput Vout []TXOutput } // IsCoinbase checks whether the transaction is a coinbase func (tx Transaction) IsCoinbase() bool { return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1 } // SetID sets the ID of a transaction func (tx Transaction) SetID() { var encoded bytes.Buffer var hash [32]byte enc := gob.NewEncoder(&encoded) err := enc.Encode(tx) if err != nil { exitWithError(err) } hash = sha256.Sum256(encoded.Bytes()) tx.ID = hash[:] } // NewCoinbaseTX creates a new coinbase transaction func NewCoinbaseTX(to, data string) *Transaction { if data == "" { data = fmt.Sprintf("Reward to '%s'", to) } txin := TXInput{ Txid: []byte{}, Vout: -1, ScriptSig: data, } txout := TXOutput{ Value: subsidy, ScriptPubKey: to, } tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} tx.SetID() return &tx } // NewUTXOTransaction creates a new transaction func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { var inputs []TXInput var outputs []TXOutput acc, validOutputs := bc.FindSpendableOutputs(from, amount) if acc < amount { fmt.Println("ERROR: Not enough funds") os.Exit(1) } // Build a list of inputs for txid, outs := range validOutputs { txID, err := hex.DecodeString(txid) if err != nil { fmt.Println(err) os.Exit(1) } for _, out := range outs { input := TXInput{ Txid: txID, Vout: out, ScriptSig: from, } inputs = append(inputs, input) } } // Build a list of outputs output := TXOutput{ Value: amount, ScriptPubKey: to, } outputs = append(outputs, output) if acc > amount { // There is some change chgOutput := TXOutput{ Value: acc - amount, ScriptPubKey: from, } outputs = append(outputs, chgOutput) } tx := Transaction{ ID: nil, Vin: inputs, Vout: outputs, } tx.SetID() return &tx } // TXInput represents a transaction input type TXInput struct { Txid []byte Vout int ScriptSig string } // CanUnlockOutputWith checks whether the unlockingData (address) initiated the transaction func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { return in.ScriptSig == unlockingData } // TXOutput represents a transaction output type TXOutput struct { Value int ScriptPubKey string } // CanBeUnlockedWith checks if the output can be unlocked with the provided data func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { return out.ScriptPubKey == unlockingData }