diff --git a/.gitignore b/.gitignore index 1b07dece..e2c8a2c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.db *.dat +cscope.* +.*.swp +blockchain_go diff --git a/README.md b/README.md index 76ae180b..d79518c0 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,29 @@ A blockchain implementation in Go, as described in these articles: 5. [Addresses](https://jeiwan.cc/posts/building-blockchain-in-go-part-5/) 6. [Transactions 2](https://jeiwan.cc/posts/building-blockchain-in-go-part-6/) 7. [Network](https://jeiwan.cc/posts/building-blockchain-in-go-part-7/) + +# Quick Start + +## Download and install + + go get github.com/richardweiyang/blockchain_go + +## Create file `main.go` + +source code + +```go +package main + +import "github.com/richardweiyang/blockchain_go" + +func main() { + cli := bc.CLI{} + cli.Run() +} +``` +#### Build and run + + go build main.go + ./main + diff --git a/base58.go b/base58.go index 5db078e4..6b7089f3 100644 --- a/base58.go +++ b/base58.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" @@ -40,8 +40,10 @@ func Base58Decode(input []byte) []byte { zeroBytes := 0 for _, b := range input { - if b == 0x00 { + if b == b58Alphabet[0] { zeroBytes++ + } else { + break } } diff --git a/base58_test.go b/base58_test.go new file mode 100644 index 00000000..8d80cd33 --- /dev/null +++ b/base58_test.go @@ -0,0 +1,27 @@ +package bc + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBase58(t *testing.T) { + for i := 0; i < 100; i++ { + _, public := newKeyPair() + pubKeyHash := HashPubKey(public) + + versionedPayload := append([]byte{version}, pubKeyHash...) + checksum := checksum(versionedPayload) + + fullPayload := append(versionedPayload, checksum...) + address := Base58Encode(fullPayload) + + assert.Equal( + t, + ValidateAddress(string(address[:])), + true, + "Address: %s is invalid", address, + ) + } +} diff --git a/block.go b/block.go index e403e549..3e90caec 100644 --- a/block.go +++ b/block.go @@ -1,9 +1,12 @@ -package main +package bc import ( "bytes" "encoding/gob" + "fmt" "log" + "strconv" + "strings" "time" ) @@ -71,3 +74,20 @@ func DeserializeBlock(d []byte) *Block { return &block } + +func (b *Block) PrintHTML(detail bool) string { + var lines []string + lines = append(lines, fmt.Sprintf("

Block %x

", b.Hash, b.Hash)) + lines = append(lines, fmt.Sprintf("Height: %d
", b.Height)) + lines = append(lines, fmt.Sprintf("Prev. block: %x
", b.PrevBlockHash, b.PrevBlockHash)) + lines = append(lines, fmt.Sprintf("Created at : %s
", time.Unix(b.Timestamp, 0))) + pow := NewProofOfWork(b) + lines = append(lines, fmt.Sprintf("PoW: %s

", strconv.FormatBool(pow.Validate()))) + if detail { + for _, tx := range b.Transactions { + lines = append(lines, tx.PrintHTML()) + } + } + lines = append(lines, fmt.Sprintf("

")) + return strings.Join(lines, "\n") +} diff --git a/blockchain.go b/blockchain.go index 8436035b..da5e78a2 100644 --- a/blockchain.go +++ b/blockchain.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" @@ -8,6 +8,7 @@ import ( "fmt" "log" "os" + "strings" "github.com/boltdb/bolt" ) @@ -19,7 +20,7 @@ const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second // Blockchain implements interactions with a DB type Blockchain struct { tip []byte - db *bolt.DB + DB *bolt.DB } // CreateBlockchain creates a new blockchain DB @@ -82,7 +83,7 @@ func NewBlockchain(nodeID string) *Blockchain { log.Panic(err) } - err = db.Update(func(tx *bolt.Tx) error { + err = db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) tip = b.Get([]byte("l")) @@ -99,7 +100,7 @@ func NewBlockchain(nodeID string) *Blockchain { // AddBlock saves the block into the blockchain func (bc *Blockchain) AddBlock(block *Block) { - err := bc.db.Update(func(tx *bolt.Tx) error { + err := bc.DB.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) blockInDb := b.Get(block.Hash) @@ -199,7 +200,7 @@ func (bc *Blockchain) FindUTXO() map[string]TXOutputs { // Iterator returns a BlockchainIterat func (bc *Blockchain) Iterator() *BlockchainIterator { - bci := &BlockchainIterator{bc.tip, bc.db} + bci := &BlockchainIterator{bc.tip, bc.DB} return bci } @@ -208,7 +209,7 @@ func (bc *Blockchain) Iterator() *BlockchainIterator { func (bc *Blockchain) GetBestHeight() int { var lastBlock Block - err := bc.db.View(func(tx *bolt.Tx) error { + err := bc.DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash := b.Get([]byte("l")) blockData := b.Get(lastHash) @@ -227,7 +228,7 @@ func (bc *Blockchain) GetBestHeight() int { func (bc *Blockchain) GetBlock(blockHash []byte) (Block, error) { var block Block - err := bc.db.View(func(tx *bolt.Tx) error { + err := bc.DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) blockData := b.Get(blockHash) @@ -277,7 +278,7 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { } } - err := bc.db.View(func(tx *bolt.Tx) error { + err := bc.DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) @@ -294,7 +295,7 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block { newBlock := NewBlock(transactions, lastHash, lastHeight+1) - err = bc.db.Update(func(tx *bolt.Tx) error { + err = bc.DB.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) if err != nil { @@ -358,3 +359,33 @@ func dbExists(dbFile string) bool { return true } + +func (bc *Blockchain) PrintHTML() string { + var lines []string + bci := bc.Iterator() + + for { + block := bci.Next() + + lines = append(lines, block.PrintHTML(false)) + + if len(block.PrevBlockHash) == 0 { + break + } + } + return strings.Join(lines, "\n") + +} + +func (bc *Blockchain) GetBalance(address string) int { + UTXOSet := UTXOSet{bc} + balance := 0 + pubKeyHash := Base58Decode([]byte(address)) + pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] + UTXOs := UTXOSet.FindUTXO(pubKeyHash) + + for _, out := range UTXOs { + balance += out.Value + } + return balance +} diff --git a/blockchain_iterator.go b/blockchain_iterator.go index 305311d9..5b3699e1 100644 --- a/blockchain_iterator.go +++ b/blockchain_iterator.go @@ -1,4 +1,4 @@ -package main +package bc import ( "log" diff --git a/cli.go b/cli.go index b597b4c7..90d7165d 100644 --- a/cli.go +++ b/cli.go @@ -1,4 +1,4 @@ -package main +package bc import ( "flag" @@ -21,6 +21,14 @@ func (cli *CLI) printUsage() { fmt.Println(" reindexutxo - Rebuilds the UTXO set") fmt.Println(" send -from FROM -to TO -amount AMOUNT -mine - Send AMOUNT of coins from FROM address to TO. Mine on the same node, when -mine is set.") fmt.Println(" startnode -miner ADDRESS - Start a node with ID specified in NODE_ID env. var. -miner enables mining") + fmt.Println() + fmt.Println("Exploring cmds:") + fmt.Println(" generatePrivKey - generate KeyPair for exploring") + fmt.Println(" getPubKey -privKey PRIKEY - generate PubKey from privateKey") + fmt.Println(" getAddress -pubKey PUBKEY - convert pubKey to address") + fmt.Println(" getPubKeyHash -address Address - get pubKeyHash of an address") + fmt.Println(" validateAddress -addr Address - validate an address") + fmt.Println(" getBlock -hash BlockHash - get a block with BlockHash") } func (cli *CLI) validateArgs() { @@ -48,6 +56,12 @@ func (cli *CLI) Run() { reindexUTXOCmd := flag.NewFlagSet("reindexutxo", flag.ExitOnError) sendCmd := flag.NewFlagSet("send", flag.ExitOnError) startNodeCmd := flag.NewFlagSet("startnode", flag.ExitOnError) + generatePrivKeyCmd := flag.NewFlagSet("generatePrivKey", flag.ExitOnError) + getPubKeyCmd := flag.NewFlagSet("getPubKey", flag.ExitOnError) + getAddressCmd := flag.NewFlagSet("getAddress", flag.ExitOnError) + getPubKeyHashCmd := flag.NewFlagSet("getPubKeyHash", flag.ExitOnError) + validateAddrCmd := flag.NewFlagSet("validateAddress", flag.ExitOnError) + getBlockCmd := flag.NewFlagSet("getBlock", flag.ExitOnError) getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to") @@ -56,6 +70,11 @@ func (cli *CLI) Run() { sendAmount := sendCmd.Int("amount", 0, "Amount to send") sendMine := sendCmd.Bool("mine", false, "Mine immediately on the same node") startNodeMiner := startNodeCmd.String("miner", "", "Enable mining mode and send reward to ADDRESS") + privateKey := getPubKeyCmd.String("privKey", "", "generate PubKey based on this") + pubKey := getAddressCmd.String("pubKey", "", "the key where address generated") + pubKeyAddress := getPubKeyHashCmd.String("address", "", "the pub address") + address := validateAddrCmd.String("addr", "", "the public address") + blockHash := getBlockCmd.String("hash", "", "the block hash") switch os.Args[1] { case "getbalance": @@ -98,6 +117,36 @@ func (cli *CLI) Run() { if err != nil { log.Panic(err) } + case "validateAddress": + err := validateAddrCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "generatePrivKey": + err := generatePrivKeyCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "getPubKey": + err := getPubKeyCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "getPubKeyHash": + err := getPubKeyHashCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "getAddress": + err := getAddressCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } + case "getBlock": + err := getBlockCmd.Parse(os.Args[2:]) + if err != nil { + log.Panic(err) + } default: cli.printUsage() os.Exit(1) @@ -152,4 +201,53 @@ func (cli *CLI) Run() { } cli.startNode(nodeID, *startNodeMiner) } + + if generatePrivKeyCmd.Parsed() { + cli.generatePrivKey() + } + + if getPubKeyCmd.Parsed() { + if *privateKey == "" { + getPubKeyCmd.Usage() + os.Exit(1) + } + cli.getPubKey(*privateKey) + } + + if getAddressCmd.Parsed() { + if *pubKey == "" { + getAddressCmd.Usage() + os.Exit(1) + } + + cli.getAddress(*pubKey) + } + + if getPubKeyHashCmd.Parsed() { + if *pubKeyAddress == "" { + getPubKeyHashCmd.Usage() + os.Exit(1) + } + + cli.getPubKeyHash(*pubKeyAddress) + } + + if validateAddrCmd.Parsed() { + if *address == "" { + validateAddrCmd.Usage() + os.Exit(1) + } + + cli.validateAddr(*address) + } + + if getBlockCmd.Parsed() { + if *blockHash == "" { + getBlockCmd.Usage() + os.Exit(1) + } + + cli.printBlock(*blockHash, nodeID) + } + } diff --git a/cli_createblockchain.go b/cli_createblockchain.go index 7e69405c..bc77fe96 100644 --- a/cli_createblockchain.go +++ b/cli_createblockchain.go @@ -1,4 +1,4 @@ -package main +package bc import ( "fmt" @@ -10,7 +10,7 @@ func (cli *CLI) createBlockchain(address, nodeID string) { log.Panic("ERROR: Address is not valid") } bc := CreateBlockchain(address, nodeID) - defer bc.db.Close() + defer bc.DB.Close() UTXOSet := UTXOSet{bc} UTXOSet.Reindex() diff --git a/cli_createwallet.go b/cli_createwallet.go index 0e05f202..c605d656 100644 --- a/cli_createwallet.go +++ b/cli_createwallet.go @@ -1,4 +1,4 @@ -package main +package bc import "fmt" diff --git a/cli_explore.go b/cli_explore.go new file mode 100644 index 00000000..d68a0e91 --- /dev/null +++ b/cli_explore.go @@ -0,0 +1,48 @@ +package bc + +import ( + "crypto/elliptic" + "encoding/hex" + "fmt" +) + +func (cli *CLI) getPubKey(privateKey string) { + curve := elliptic.P256() + priv_key, _ := hex.DecodeString(privateKey) + x, y := curve.ScalarBaseMult(priv_key) + pubKey := append(x.Bytes(), y.Bytes()...) + fmt.Println(hex.EncodeToString(pubKey)) +} + +func (cli *CLI) generatePrivKey() { + private, _ := newKeyPair() + fmt.Println(hex.EncodeToString(private.D.Bytes())) +} + +func (cli *CLI) getAddress(pubKey string) { + public, _ := hex.DecodeString(pubKey) + + pubKeyHash := HashPubKey(public) + + versionedPayload := append([]byte{version}, pubKeyHash...) + fullPayload := append(versionedPayload, checksum(versionedPayload)...) + + fmt.Println() + fmt.Printf("PubKey : %s\n", pubKey) + fmt.Printf("PubKeyHash : %x\n", pubKeyHash) + fmt.Printf("Address : %s\n", Base58Encode(fullPayload)) +} + +func (cli *CLI) getPubKeyHash(address string) { + pubKeyHash := Base58Decode([]byte(address)) + fmt.Printf("%x\n", pubKeyHash[1:len(pubKeyHash)-4]) +} + +func (cli *CLI) validateAddr(address string) { + fmt.Printf("Address: %s\n", address) + if !ValidateAddress(address) { + fmt.Println("Not valid!") + } else { + fmt.Println("Valid!") + } +} diff --git a/cli_getbalance.go b/cli_getbalance.go index a86e3dc1..9aa7c0f1 100644 --- a/cli_getbalance.go +++ b/cli_getbalance.go @@ -1,4 +1,4 @@ -package main +package bc import ( "fmt" @@ -11,7 +11,7 @@ func (cli *CLI) getBalance(address, nodeID string) { } bc := NewBlockchain(nodeID) UTXOSet := UTXOSet{bc} - defer bc.db.Close() + defer bc.DB.Close() balance := 0 pubKeyHash := Base58Decode([]byte(address)) diff --git a/cli_listaddress.go b/cli_listaddress.go index 0d30563d..66bdf1e4 100644 --- a/cli_listaddress.go +++ b/cli_listaddress.go @@ -1,4 +1,4 @@ -package main +package bc import ( "fmt" diff --git a/cli_printchain.go b/cli_printchain.go index 81b7edf7..7aaa63fb 100644 --- a/cli_printchain.go +++ b/cli_printchain.go @@ -1,13 +1,14 @@ -package main +package bc import ( "fmt" "strconv" + "time" ) func (cli *CLI) printChain(nodeID string) { bc := NewBlockchain(nodeID) - defer bc.db.Close() + defer bc.DB.Close() bci := bc.Iterator() @@ -17,6 +18,7 @@ func (cli *CLI) printChain(nodeID string) { fmt.Printf("============ Block %x ============\n", block.Hash) fmt.Printf("Height: %d\n", block.Height) fmt.Printf("Prev. block: %x\n", block.PrevBlockHash) + fmt.Printf("Created at : %s\n", time.Unix(block.Timestamp, 0)) pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate())) for _, tx := range block.Transactions { @@ -29,3 +31,32 @@ func (cli *CLI) printChain(nodeID string) { } } } + +func (cli *CLI) printBlock(blockHash, nodeID string) { + bc := NewBlockchain(nodeID) + defer bc.DB.Close() + + bci := bc.Iterator() + + for { + block := bci.Next() + + hash := fmt.Sprintf("%x", block.Hash) + if hash == blockHash { + fmt.Printf("============ Block %x ============\n", block.Hash) + fmt.Printf("Height: %d\n", block.Height) + fmt.Printf("Prev. block: %x\n", block.PrevBlockHash) + fmt.Printf("Created at : %s\n", time.Unix(block.Timestamp, 0)) + pow := NewProofOfWork(block) + fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate())) + for _, tx := range block.Transactions { + fmt.Println(tx) + } + fmt.Printf("\n\n") + } + + if len(block.PrevBlockHash) == 0 { + break + } + } +} diff --git a/cli_reindexutxo.go b/cli_reindexutxo.go index 87db3245..74b8dc52 100644 --- a/cli_reindexutxo.go +++ b/cli_reindexutxo.go @@ -1,4 +1,4 @@ -package main +package bc import "fmt" diff --git a/cli_send.go b/cli_send.go index 75a59301..f2f58767 100644 --- a/cli_send.go +++ b/cli_send.go @@ -1,4 +1,4 @@ -package main +package bc import ( "fmt" @@ -15,15 +15,19 @@ func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) { bc := NewBlockchain(nodeID) UTXOSet := UTXOSet{bc} - defer bc.db.Close() + defer bc.DB.Close() wallets, err := NewWallets(nodeID) if err != nil { log.Panic(err) } wallet := wallets.GetWallet(from) + if wallet == nil { + fmt.Println("The Address doesn't belongs to you!") + return + } - tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet) + tx := NewUTXOTransaction(wallet, to, amount, &UTXOSet) if mineNow { cbTx := NewCoinbaseTX(from, "") diff --git a/cli_startnode.go b/cli_startnode.go index d390512e..9d6b5814 100644 --- a/cli_startnode.go +++ b/cli_startnode.go @@ -1,4 +1,4 @@ -package main +package bc import ( "fmt" diff --git a/main.go b/main.go deleted file mode 100644 index a48ad8e5..00000000 --- a/main.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -func main() { - cli := CLI{} - cli.Run() -} diff --git a/merkle_tree.go b/merkle_tree.go index 7a4156bd..985dde45 100644 --- a/merkle_tree.go +++ b/merkle_tree.go @@ -1,4 +1,4 @@ -package main +package bc import ( "crypto/sha256" @@ -20,7 +20,8 @@ type MerkleNode struct { func NewMerkleTree(data [][]byte) *MerkleTree { var nodes []MerkleNode - if len(data)%2 != 0 { + // append node until number is power of 2 + for (len(data) & (len(data) - 1)) != 0 { data = append(data, data[len(data)-1]) } @@ -29,7 +30,8 @@ func NewMerkleTree(data [][]byte) *MerkleTree { nodes = append(nodes, *node) } - for i := 0; i < len(data)/2; i++ { + // up level until there is only 1 node + for len(nodes) > 1 { var newLevel []MerkleNode for j := 0; j < len(nodes); j += 2 { diff --git a/merkle_tree_test.go b/merkle_tree_test.go index acff5ffa..5275a5e1 100644 --- a/merkle_tree_test.go +++ b/merkle_tree_test.go @@ -1,4 +1,4 @@ -package main +package bc import ( "encoding/hex" @@ -73,3 +73,41 @@ func TestNewMerkleTree(t *testing.T) { assert.Equal(t, rootHash, fmt.Sprintf("%x", mTree.RootNode.Data), "Merkle tree root hash is correct") } + +func TestNewMerkleTree8(t *testing.T) { + data := [][]byte{ + []byte("node1"), + []byte("node2"), + []byte("node3"), + []byte("node4"), + []byte("node5"), + []byte("node6"), + } + // Level 0 + n01 := NewMerkleNode(nil, nil, data[0]) + n02 := NewMerkleNode(nil, nil, data[1]) + n03 := NewMerkleNode(nil, nil, data[2]) + n04 := NewMerkleNode(nil, nil, data[3]) + n05 := NewMerkleNode(nil, nil, data[4]) + n06 := NewMerkleNode(nil, nil, data[5]) + n07 := NewMerkleNode(nil, nil, data[5]) + n08 := NewMerkleNode(nil, nil, data[5]) + + // Level 1 + n11 := NewMerkleNode(n01, n02, nil) + n12 := NewMerkleNode(n03, n04, nil) + n13 := NewMerkleNode(n05, n06, nil) + n14 := NewMerkleNode(n07, n08, nil) + + // Level 2 + n21 := NewMerkleNode(n11, n12, nil) + n22 := NewMerkleNode(n13, n14, nil) + + // Level 3 + n31 := NewMerkleNode(n21, n22, nil) + + rootHash := fmt.Sprintf("%x", n31.Data) + mTree := NewMerkleTree(data) + + assert.Equal(t, rootHash, fmt.Sprintf("%x", mTree.RootNode.Data), "Merkle tree root hash is correct") +} diff --git a/proofofwork.go b/proofofwork.go index 967a4bc1..2160cbe2 100644 --- a/proofofwork.go +++ b/proofofwork.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" diff --git a/server.go b/server.go index b13f520b..0895ad98 100644 --- a/server.go +++ b/server.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" @@ -341,7 +341,7 @@ func handleTx(request []byte, bc *Blockchain) { UTXOSet := UTXOSet{bc} UTXOSet.Reindex() - fmt.Println("New block is mined!") + fmt.Printf("New block with %d tx is mined!\n", len(txs)) for _, tx := range txs { txID := hex.EncodeToString(tx.ID) @@ -383,6 +383,7 @@ func handleVersion(request []byte, bc *Blockchain) { // sendAddr(payload.AddrFrom) if !nodeIsKnown(payload.AddrFrom) { + fmt.Printf("A new node %s is connected\n", payload.AddrFrom) knownNodes = append(knownNodes, payload.AddrFrom) } } diff --git a/transaction.go b/transaction.go index f2031d3d..bf1b1bc4 100644 --- a/transaction.go +++ b/transaction.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" @@ -94,17 +94,23 @@ func (tx Transaction) String() string { for i, input := range tx.Vin { + pubKeyHash := HashPubKey(input.PubKey) + versionedPayload := append([]byte{version}, pubKeyHash...) + fullPayload := append(versionedPayload, checksum(versionedPayload)...) + lines = append(lines, fmt.Sprintf(" Input %d:", i)) lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid)) lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout)) lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) + lines = append(lines, fmt.Sprintf(" Addr : %s", Base58Encode(fullPayload))) } for i, output := range tx.Vout { lines = append(lines, fmt.Sprintf(" Output %d:", i)) lines = append(lines, fmt.Sprintf(" Value: %d", output.Value)) lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) + lines = append(lines, fmt.Sprintf(" Addr : %s", output.Address)) } return strings.Join(lines, "\n") @@ -120,7 +126,7 @@ func (tx *Transaction) TrimmedCopy() Transaction { } for _, vout := range tx.Vout { - outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) + outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash, vout.Address}) } txCopy := Transaction{tx.ID, inputs, outputs} @@ -243,3 +249,32 @@ func DeserializeTransaction(data []byte) Transaction { return transaction } + +func (tx Transaction) PrintHTML() string { + var lines []string + + lines = append(lines, fmt.Sprintf("

Transaction %x:

", tx.ID)) + + for i, input := range tx.Vin { + + pubKeyHash := HashPubKey(input.PubKey) + versionedPayload := append([]byte{version}, pubKeyHash...) + fullPayload := append(versionedPayload, checksum(versionedPayload)...) + + lines = append(lines, fmt.Sprintf("
Input %d:
", i)) + lines = append(lines, fmt.Sprintf("
TXID: %x
", input.Txid)) + lines = append(lines, fmt.Sprintf("
Out: %d
", input.Vout)) + lines = append(lines, fmt.Sprintf("
Signature: %x
", input.Signature)) + lines = append(lines, fmt.Sprintf("
PubKey: %x
", input.PubKey)) + lines = append(lines, fmt.Sprintf("
Addr : %s
", Base58Encode(fullPayload))) + } + + for i, output := range tx.Vout { + lines = append(lines, fmt.Sprintf("
Output %d:
", i)) + lines = append(lines, fmt.Sprintf("
Value: %d
", output.Value)) + lines = append(lines, fmt.Sprintf("
Script: %x
", output.PubKeyHash)) + lines = append(lines, fmt.Sprintf("
Addr : %s
", output.Address)) + } + + return strings.Join(lines, "") +} diff --git a/transaction_input.go b/transaction_input.go index 23beeba5..e928ef6c 100644 --- a/transaction_input.go +++ b/transaction_input.go @@ -1,4 +1,4 @@ -package main +package bc import "bytes" diff --git a/transaction_output.go b/transaction_output.go index 2ae68dec..d3163e81 100644 --- a/transaction_output.go +++ b/transaction_output.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" @@ -10,6 +10,7 @@ import ( type TXOutput struct { Value int PubKeyHash []byte + Address string } // Lock signs the output @@ -26,7 +27,7 @@ func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { // NewTXOutput create a new TXOutput func NewTXOutput(value int, address string) *TXOutput { - txo := &TXOutput{value, nil} + txo := &TXOutput{value, nil, address} txo.Lock([]byte(address)) return txo diff --git a/utils.go b/utils.go index aecf9192..bd1649f1 100644 --- a/utils.go +++ b/utils.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" diff --git a/utxo_set.go b/utxo_set.go index 180ce04b..7adbad9f 100644 --- a/utxo_set.go +++ b/utxo_set.go @@ -1,4 +1,4 @@ -package main +package bc import ( "encoding/hex" @@ -18,7 +18,7 @@ type UTXOSet struct { func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) accumulated := 0 - db := u.Blockchain.db + db := u.Blockchain.DB err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) @@ -48,7 +48,7 @@ func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[s // FindUTXO finds UTXO for a public key hash func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { var UTXOs []TXOutput - db := u.Blockchain.db + db := u.Blockchain.DB err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) @@ -75,7 +75,7 @@ func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput { // CountTransactions returns the number of transactions in the UTXO set func (u UTXOSet) CountTransactions() int { - db := u.Blockchain.db + db := u.Blockchain.DB counter := 0 err := db.View(func(tx *bolt.Tx) error { @@ -97,7 +97,7 @@ func (u UTXOSet) CountTransactions() int { // Reindex rebuilds the UTXO set func (u UTXOSet) Reindex() { - db := u.Blockchain.db + db := u.Blockchain.DB bucketName := []byte(utxoBucket) err := db.Update(func(tx *bolt.Tx) error { @@ -141,7 +141,7 @@ func (u UTXOSet) Reindex() { // Update updates the UTXO set with transactions from the Block // The Block is considered to be the tip of a blockchain func (u UTXOSet) Update(block *Block) { - db := u.Blockchain.db + db := u.Blockchain.DB err := db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(utxoBucket)) diff --git a/wallet.go b/wallet.go index 506b5444..e1bd6a34 100644 --- a/wallet.go +++ b/wallet.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" diff --git a/wallets.go b/wallets.go index 9f376f53..763d3547 100644 --- a/wallets.go +++ b/wallets.go @@ -1,4 +1,4 @@ -package main +package bc import ( "bytes" @@ -49,8 +49,8 @@ func (ws *Wallets) GetAddresses() []string { } // GetWallet returns a Wallet by its address -func (ws Wallets) GetWallet(address string) Wallet { - return *ws.Wallets[address] +func (ws Wallets) GetWallet(address string) *Wallet { + return ws.Wallets[address] } // LoadFromFile loads wallets from the file