commit 93685f2ed7db82624f968d9c65c529f0257a6ec0 Author: SowinskiBraeden Date: Mon Nov 28 09:22:26 2022 -0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c2f433 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp \ No newline at end of file diff --git a/blockchain/block.go b/blockchain/block.go new file mode 100644 index 0000000..809e981 --- /dev/null +++ b/blockchain/block.go @@ -0,0 +1,58 @@ +package blockchain + +import ( + "bytes" + "encoding/gob" + "log" +) + +type Block struct { + Hash []byte + Data []byte + PrevHash []byte + Nonce int +} + +func CreateBlock(data string, prevHash []byte) *Block { + block := &Block{[]byte{}, []byte(data), prevHash, 0} + pow := NewProof(block) + nonce, hash := pow.Run() + + block.Hash = hash[:] + block.Nonce = nonce + + return block +} + +func Genesis() *Block { + return CreateBlock("Genesis", []byte{}) +} + +func (b *Block) Serialize() []byte { + var res bytes.Buffer + encoder := gob.NewEncoder(&res) + + err := encoder.Encode(b) + + Handle(err) + + return res.Bytes() +} + +func Deserialize(data []byte) *Block { + var block Block + + decoder := gob.NewDecoder(bytes.NewReader(data)) + + err := decoder.Decode(&block) + + Handle(err) + + return &block +} + +func Handle(err error) { + if err != nil { + log.Panic(err) + } +} diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go new file mode 100644 index 0000000..846c287 --- /dev/null +++ b/blockchain/blockchain.go @@ -0,0 +1,105 @@ +package blockchain + +import ( + "fmt" + + "github.com/dgraph-io/badger" +) + +const ( + dbPath = "./tmp/blocks" +) + +type BlockChain struct { + LastHash []byte + Database *badger.DB +} + +type BlockChainIterator struct { + CurrentHash []byte + Database *badger.DB +} + +func InitBlockChain() *BlockChain { + var lastHash []byte + + opts := badger.DefaultOptions(dbPath) + opts.Logger = nil + db, err := badger.Open(opts) + Handle(err) + + err = db.Update(func(txn *badger.Txn) error { + if _, err := txn.Get([]byte("lh")); err == badger.ErrKeyNotFound { + fmt.Println("No existing blockchain found") + genesis := Genesis() + fmt.Println("Genesis proved") + err = txn.Set(genesis.Hash, genesis.Serialize()) + Handle(err) + err = txn.Set([]byte("lh"), genesis.Hash) + + lastHash = genesis.Hash + + return err + } else { + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, err = item.ValueCopy(nil) + return err + } + }) + + Handle(err) + + blockchain := BlockChain{lastHash, db} + return &blockchain +} + +func (chain *BlockChain) AddBlock(data string) { + var lastHash []byte + + err := chain.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("lh")) + Handle(err) + lastHash, err = item.ValueCopy(nil) + + return err + }) + Handle(err) + + newBlock := CreateBlock(data, lastHash) + + err = chain.Database.Update(func(txn *badger.Txn) error { + err := txn.Set(newBlock.Hash, newBlock.Serialize()) + Handle(err) + err = txn.Set([]byte("lh"), newBlock.Hash) + + chain.LastHash = newBlock.Hash + + return err + }) + Handle(err) +} + +func (chain *BlockChain) Iterator() *BlockChainIterator { + iter := &BlockChainIterator{chain.LastHash, chain.Database} + + return iter +} + +func (iter *BlockChainIterator) Next() *Block { + var block *Block + + err := iter.Database.View(func(txn *badger.Txn) error { + item, err := txn.Get(iter.CurrentHash) + Handle(err) + encodedBlock, err := item.ValueCopy(nil) + block = Deserialize(encodedBlock) + + return err + }) + Handle(err) + + iter.CurrentHash = block.PrevHash + + return block +} diff --git a/blockchain/proof.go b/blockchain/proof.go new file mode 100644 index 0000000..d1d1e9a --- /dev/null +++ b/blockchain/proof.go @@ -0,0 +1,97 @@ +package blockchain + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "fmt" + "log" + "math" + "math/big" +) + +// Take the data from the block + +// Create a counter (nonce) which starts at 0 + +// Create a hash of the data plus the counter + +// Check the hash to see if it meets a set of requireents + +// Requirements: +// The first few bytes must contain 0s + +const Difficulty = 18 + +type ProofOfWork struct { + Block *Block + Target *big.Int +} + +func NewProof(b *Block) *ProofOfWork { + target := big.NewInt(1) + target.Lsh(target, uint(256-Difficulty)) + + pow := &ProofOfWork{b, target} + + return pow +} + +func (pow *ProofOfWork) InitData(nonce int) []byte { + data := bytes.Join( + [][]byte{ + pow.Block.PrevHash, + pow.Block.Data, + ToHex(int64(nonce)), + ToHex(int64(Difficulty)), + }, + []byte{}, + ) + + return data +} + +func (pow *ProofOfWork) Run() (int, []byte) { + var intHash big.Int + var hash [32]byte + + nonce := 0 + + for nonce < math.MaxInt64 { + data := pow.InitData(nonce) + hash = sha256.Sum256(data) + + fmt.Printf("\r%x", hash) + intHash.SetBytes(hash[:]) + + if intHash.Cmp(pow.Target) == -1 { + break + } else { + nonce++ + } + } + fmt.Println() + + return nonce, hash[:] +} + +func (pow *ProofOfWork) Validate() bool { + var intHash big.Int + + data := pow.InitData(pow.Block.Nonce) + + hash := sha256.Sum256(data) + intHash.SetBytes(hash[:]) + + return intHash.Cmp(pow.Target) == -1 +} + +func ToHex(num int64) []byte { + buff := new(bytes.Buffer) + err := binary.Write(buff, binary.BigEndian, num) + if err != nil { + log.Panic(err) + } + + return buff.Bytes() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9033710 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/SowinskiBraeden/golang-blockchain + +go 1.19 + +require github.com/dgraph-io/badger v1.6.2 + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/dgraph-io/ristretto v0.0.2 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/golang/protobuf v1.3.1 // indirect + github.com/pkg/errors v0.8.1 // indirect + golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect + golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..25900bb --- /dev/null +++ b/go.sum @@ -0,0 +1,68 @@ +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= +github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= +github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0a98c1b --- /dev/null +++ b/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + "strconv" + + "github.com/SowinskiBraeden/golang-blockchain/blockchain" +) + +type CommandLine struct { + blockchain *blockchain.BlockChain +} + +func (cli *CommandLine) printUsage() { + fmt.Println("Usage:") + fmt.Println(" add -block BLOCK_DATA - add a block to the chain") + fmt.Println(" print - Prints the blocks in the chain") +} + +func (cli *CommandLine) validateArgs() { + if len(os.Args) < 2 { + cli.printUsage() + runtime.Goexit() + } +} + +func (cli *CommandLine) addBlock(data string) { + cli.blockchain.AddBlock(data) + fmt.Println("Added Block!") +} + +func (cli *CommandLine) run() { + cli.validateArgs() + + addBlockCmd := flag.NewFlagSet("add", flag.ExitOnError) + printChainCmd := flag.NewFlagSet("print", flag.ExitOnError) + addBlockData := addBlockCmd.String("block", "", "Block data") + + switch os.Args[1] { + case "add": + err := addBlockCmd.Parse(os.Args[2:]) + blockchain.Handle(err) + + case "print": + err := printChainCmd.Parse(os.Args[2:]) + blockchain.Handle(err) + + default: + cli.printUsage() + runtime.Goexit() + } + + if addBlockCmd.Parsed() { + if *addBlockData == "" { + addBlockCmd.Usage() + runtime.Goexit() + } + + cli.addBlock(*addBlockData) + } + + if printChainCmd.Parsed() { + cli.printChain() + } +} + +func (cli *CommandLine) printChain() { + iter := cli.blockchain.Iterator() + + for { + block := iter.Next() + + fmt.Printf("Prev. Hash: %x\n", block.PrevHash) + fmt.Printf("Data: %s\n", block.Data) + fmt.Printf("Hash: %x\n", block.Hash) + pow := blockchain.NewProof(block) + fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) + fmt.Println() + + if len(block.PrevHash) == 0 { + break + } + } +} + +func main() { + defer os.Exit(0) + chain := blockchain.InitBlockChain() + defer chain.Database.Close() + + cli := CommandLine{chain} + cli.run() +}