move elkrem, uspv libs to plasma repo
This commit is contained in:
parent
b0ce9a06dc
commit
65c7d1c40c
157
elkrem/elkrem.go
Normal file
157
elkrem/elkrem.go
Normal file
@ -0,0 +1,157 @@
|
||||
package elkrem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
/* elkrem is a simpler alternative to the 64 dimensional sha-chain.
|
||||
it's basically a reverse merkle tree. If we want to provide 2**64 possible
|
||||
hashes, this requires a worst case computation of 63 hashes for the
|
||||
sender, and worst-case storage of 64 hashes for the receiver.
|
||||
|
||||
The operations are left hash L() and right hash R(), which are
|
||||
hash(parent) and hash(parent, 1) respectively. (concatenate one byte)
|
||||
|
||||
Here is a shorter example of a tree with 8 leaves and 15 total nodes.
|
||||
|
||||
The sender first computes the bottom left leaf 0b0000. This is
|
||||
L(L(L(L(root)))). The receiver stores leaf 0.
|
||||
|
||||
Next the sender computes 0b0001. R(L(L(L(root)))). Receiver stores.
|
||||
Next sender computes 0b1000 (8). L(L(L(root))). Receiver stores this, and
|
||||
discards leaves 0b0000 and 0b0001, as they have the parent node 8.
|
||||
|
||||
For total hashes (2**h)-1 requires a tree of height h.
|
||||
|
||||
Sender:
|
||||
as state, must store 1 hash (root) and current index (h bits)
|
||||
to move to the next index, compute at most h hashes.
|
||||
|
||||
Receiver:
|
||||
as state, must store at most h+1 hashes and the index of each hash (h*(h+1)) bits
|
||||
to compute a previous index, compute at most h hashes.
|
||||
*/
|
||||
|
||||
// You can calculate h from i but I can't figure out how without taking
|
||||
// O(i) ops. Feels like there should be a clever O(h) way. 1 byte, whatever.
|
||||
type ElkremNode struct {
|
||||
i uint64 // index (ith node)
|
||||
h uint8 // height of this node
|
||||
sha *wire.ShaHash // hash
|
||||
}
|
||||
type ElkremSender struct {
|
||||
current uint64 // last sent hash index
|
||||
treeHeight uint8 // height of tree (size is 2**height -1 )
|
||||
maxIndex uint64 // top of the tree
|
||||
root *wire.ShaHash // root hash of the tree
|
||||
}
|
||||
type ElkremReceiver struct {
|
||||
current uint64 // last received index (actually don't need it?)
|
||||
treeHeight uint8 // height of tree (size is 2**height -1 )
|
||||
s []ElkremNode // store of received hashes, max size = height
|
||||
}
|
||||
|
||||
func LeftSha(in wire.ShaHash) wire.ShaHash {
|
||||
return wire.DoubleSha256SH(in.Bytes()) // left is sha(sha(in))
|
||||
}
|
||||
func RightSha(in wire.ShaHash) wire.ShaHash {
|
||||
return wire.DoubleSha256SH(append(in.Bytes(), 0x01)) // sha(sha(in, 1))
|
||||
}
|
||||
|
||||
// iterative descent of sub-tree. w = hash number you want. i = input index
|
||||
// h = height of input index. sha = input hash
|
||||
func descend(w, i uint64, h uint8, sha wire.ShaHash) (wire.ShaHash, error) {
|
||||
for w < i {
|
||||
if w <= i-(1<<h) { // left
|
||||
sha = LeftSha(sha)
|
||||
i = i - (1 << h) // left descent reduces index by 2**h
|
||||
} else { // right
|
||||
sha = RightSha(sha)
|
||||
i-- // right descent reduces index by 1
|
||||
}
|
||||
if h == 0 { // avoid underflowing h
|
||||
break
|
||||
}
|
||||
h-- // either descent reduces height by 1
|
||||
}
|
||||
if w != i { // somehow couldn't / didn't end up where we wanted to go
|
||||
return sha, fmt.Errorf("can't generate index %d from %d", w, i)
|
||||
}
|
||||
return sha, nil
|
||||
}
|
||||
|
||||
// Creates an Elkrem Sender from a root hash and tree height
|
||||
func NewElkremSender(th uint8, r wire.ShaHash) ElkremSender {
|
||||
var e ElkremSender
|
||||
e.root = &r
|
||||
e.treeHeight = th
|
||||
// set max index based on tree height
|
||||
for j := uint8(0); j <= e.treeHeight; j++ {
|
||||
e.maxIndex = e.maxIndex<<1 | 1
|
||||
}
|
||||
e.maxIndex-- // 1 less than 2**h
|
||||
return e
|
||||
}
|
||||
|
||||
// Creates an Elkrem Receiver from a tree height
|
||||
func NewElkremReceiver(th uint8) ElkremReceiver {
|
||||
var e ElkremReceiver
|
||||
e.treeHeight = th
|
||||
return e
|
||||
}
|
||||
|
||||
// Next() increments the index to the next hash and outputs it
|
||||
func (e *ElkremSender) Next() (*wire.ShaHash, error) {
|
||||
// increment index
|
||||
e.current++
|
||||
return e.AtIndex(e.current)
|
||||
}
|
||||
|
||||
// w is the wanted index, i is the root index
|
||||
func (e *ElkremSender) AtIndex(w uint64) (*wire.ShaHash, error) {
|
||||
out, err := descend(w, e.maxIndex, e.treeHeight, *e.root)
|
||||
return &out, err
|
||||
}
|
||||
|
||||
func (e *ElkremReceiver) AddNext(sha *wire.ShaHash) error {
|
||||
// note: careful about atomicity / disk writes here
|
||||
var n ElkremNode
|
||||
n.sha = sha
|
||||
t := len(e.s) - 1 // top of stack
|
||||
if t > 0 && e.s[t-1].h == e.s[t].h { // top 2 elements are equal height
|
||||
// next node must be parent; verify and remove children
|
||||
n.h = e.s[t].h + 1 // assign height
|
||||
l := LeftSha(*sha) // calc l child
|
||||
r := RightSha(*sha) // calc r child
|
||||
if !e.s[t-1].sha.IsEqual(&l) { // test l child
|
||||
return fmt.Errorf("left child doesn't match, expect %s got %s",
|
||||
e.s[t-1].sha.String(), l.String())
|
||||
}
|
||||
if !e.s[t].sha.IsEqual(&r) { // test r child
|
||||
return fmt.Errorf("right child doesn't match, expect %s got %s",
|
||||
e.s[t].sha.String(), r.String())
|
||||
}
|
||||
e.s = e.s[:len(e.s)-2] // l and r children OK, remove them
|
||||
} // if that didn't happen, height defaults to 0
|
||||
e.current++ // increment current index
|
||||
n.i = e.current // set new node to that incremented index
|
||||
e.s = append(e.s, n) // append new node to stack
|
||||
return nil
|
||||
}
|
||||
func (e *ElkremReceiver) AtIndex(w uint64) (*wire.ShaHash, error) {
|
||||
var out ElkremNode // node we will eventually return
|
||||
for _, n := range e.s { // go through stack
|
||||
if w <= n.i { // found one bigger than or equal to what we want
|
||||
out = n
|
||||
break
|
||||
}
|
||||
}
|
||||
if out.sha == nil { // didn't find anything
|
||||
return nil, fmt.Errorf("receiver has max %d, less than requested %d",
|
||||
e.s[len(e.s)-1].i, w)
|
||||
}
|
||||
sha, err := descend(w, out.i, out.h, *out.sha)
|
||||
return &sha, err
|
||||
}
|
263
uspv/eight333.go
Normal file
263
uspv/eight333.go
Normal file
@ -0,0 +1,263 @@
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil/bloom"
|
||||
)
|
||||
|
||||
const (
|
||||
keyFileName = "testseed.hex"
|
||||
headerFileName = "headers.bin"
|
||||
// Except hash-160s, those aren't backwards. But anything that's 32 bytes is.
|
||||
// because, cmon, 32? Gotta reverse that. But 20? 20 is OK.
|
||||
NETVERSION = wire.TestNetL
|
||||
VERSION = 70011
|
||||
)
|
||||
|
||||
var (
|
||||
params = &chaincfg.TestNet3Params
|
||||
)
|
||||
|
||||
type SPVCon struct {
|
||||
con net.Conn // the (probably tcp) connection to the node
|
||||
headerFile *os.File // file for SPV headers
|
||||
|
||||
localVersion uint32 // version we report
|
||||
remoteVersion uint32 // version remote node
|
||||
remoteHeight int32 // block height they're on
|
||||
netType wire.BitcoinNet
|
||||
|
||||
// what's the point of the input queue? remove? leave for now...
|
||||
inMsgQueue chan wire.Message // Messages coming in from remote node
|
||||
outMsgQueue chan wire.Message // Messages going out to remote node
|
||||
|
||||
WBytes uint64 // total bytes written
|
||||
RBytes uint64 // total bytes read
|
||||
}
|
||||
|
||||
func (s *SPVCon) Open(remoteNode string, hfn string) error {
|
||||
// open header file
|
||||
err := s.openHeaderFile(headerFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// open TCP connection
|
||||
s.con, err = net.Dial("tcp", remoteNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.localVersion = VERSION
|
||||
s.netType = NETVERSION
|
||||
|
||||
myMsgVer, err := wire.NewMsgVersionFromConn(s.con, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = myMsgVer.AddUserAgent("test", "zero")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// must set this to enable SPV stuff
|
||||
myMsgVer.AddService(wire.SFNodeBloom)
|
||||
|
||||
// this actually sends
|
||||
n, err := wire.WriteMessageN(s.con, myMsgVer, s.localVersion, s.netType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.WBytes += uint64(n)
|
||||
log.Printf("wrote %d byte version message to %s\n",
|
||||
n, s.con.RemoteAddr().String())
|
||||
|
||||
n, m, b, err := wire.ReadMessageN(s.con, s.localVersion, s.netType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.RBytes += uint64(n)
|
||||
log.Printf("got %d byte response %x\n command: %s\n", n, b, m.Command())
|
||||
|
||||
mv, ok := m.(*wire.MsgVersion)
|
||||
if ok {
|
||||
log.Printf("connected to %s", mv.UserAgent)
|
||||
}
|
||||
|
||||
log.Printf("remote reports version %x (dec %d)\n",
|
||||
mv.ProtocolVersion, mv.ProtocolVersion)
|
||||
|
||||
mva := wire.NewMsgVerAck()
|
||||
n, err = wire.WriteMessageN(s.con, mva, s.localVersion, s.netType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.WBytes += uint64(n)
|
||||
|
||||
s.inMsgQueue = make(chan wire.Message)
|
||||
go s.incomingMessageHandler()
|
||||
s.outMsgQueue = make(chan wire.Message)
|
||||
go s.outgoingMessageHandler()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SPVCon) openHeaderFile(hfn string) error {
|
||||
_, err := os.Stat(hfn)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
var b bytes.Buffer
|
||||
err = params.GenesisBlock.Header.Serialize(&b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(hfn, b.Bytes(), 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("created hardcoded genesis header at %s\n",
|
||||
hfn)
|
||||
}
|
||||
}
|
||||
|
||||
s.headerFile, err = os.OpenFile(hfn, os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("opened header file %s\n", s.headerFile.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SPVCon) PongBack(nonce uint64) {
|
||||
mpong := wire.NewMsgPong(nonce)
|
||||
|
||||
s.outMsgQueue <- mpong
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SPVCon) SendFilter(f *bloom.Filter) {
|
||||
s.outMsgQueue <- f.MsgFilterLoad()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SPVCon) GrabHeaders() error {
|
||||
var hdr wire.BlockHeader
|
||||
ghdr := wire.NewMsgGetHeaders()
|
||||
ghdr.ProtocolVersion = s.localVersion
|
||||
|
||||
info, err := s.headerFile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headerFileSize := info.Size()
|
||||
if headerFileSize == 0 || headerFileSize%80 != 0 { // header file broken
|
||||
return fmt.Errorf("Header file not a multiple of 80 bytes")
|
||||
}
|
||||
|
||||
// seek to 80 bytes from end of file
|
||||
ns, err := s.headerFile.Seek(-80, os.SEEK_END)
|
||||
if err != nil {
|
||||
log.Printf("can't seek\n")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("suk to offset %d (should be near the end\n", ns)
|
||||
// get header from last 80 bytes of file
|
||||
err = hdr.Deserialize(s.headerFile)
|
||||
if err != nil {
|
||||
log.Printf("can't Deserialize")
|
||||
return err
|
||||
}
|
||||
|
||||
cHash := hdr.BlockSha()
|
||||
err = ghdr.AddBlockLocatorHash(&cHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("get headers message has %d header hashes, first one is %s\n",
|
||||
len(ghdr.BlockLocatorHashes), ghdr.BlockLocatorHashes[0].String())
|
||||
|
||||
s.outMsgQueue <- ghdr
|
||||
|
||||
return nil
|
||||
// =============================================================
|
||||
|
||||
// ask for headers. probably will get 2000.
|
||||
log.Printf("getheader version %d \n", ghdr.ProtocolVersion)
|
||||
|
||||
n, m, _, err := wire.ReadMessageN(s.con, VERSION, NETVERSION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("4got %d byte response\n command: %s\n", n, m.Command())
|
||||
hdrresponse, ok := m.(*wire.MsgHeaders)
|
||||
if !ok {
|
||||
log.Printf("got non-header message.")
|
||||
return nil
|
||||
// this can acutally happen and we should deal with / ignore it
|
||||
// also pings, they don't like it when you don't respond to pings.
|
||||
// invs and the rest we can ignore for now until filters are up.
|
||||
}
|
||||
|
||||
_, err = s.headerFile.Seek(-80, os.SEEK_END)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var last wire.BlockHeader
|
||||
err = last.Deserialize(s.headerFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prevHash := last.BlockSha()
|
||||
|
||||
gotNum := int64(len(hdrresponse.Headers))
|
||||
if gotNum > 0 {
|
||||
fmt.Printf("got %d headers. Range:\n%s - %s\n",
|
||||
gotNum, hdrresponse.Headers[0].BlockSha().String(),
|
||||
hdrresponse.Headers[len(hdrresponse.Headers)-1].BlockSha().String())
|
||||
}
|
||||
_, err = s.headerFile.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, resphdr := range hdrresponse.Headers {
|
||||
// check first header returned to make sure it fits on the end
|
||||
// of our header file
|
||||
if i == 0 && !resphdr.PrevBlock.IsEqual(&prevHash) {
|
||||
return fmt.Errorf("header doesn't fit. points to %s, expect %s",
|
||||
resphdr.PrevBlock.String(), prevHash.String())
|
||||
}
|
||||
|
||||
err = resphdr.Serialize(s.headerFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
endPos, _ := s.headerFile.Seek(0, os.SEEK_END)
|
||||
tip := endPos / 80
|
||||
|
||||
go CheckRange(s.headerFile, tip-gotNum, tip-1, params)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendMBReq(cn net.Conn, blkhash wire.ShaHash) error {
|
||||
iv1 := wire.NewInvVect(wire.InvTypeFilteredBlock, &blkhash)
|
||||
gdataB := wire.NewMsgGetData()
|
||||
_ = gdataB.AddInvVect(iv1)
|
||||
n, err := wire.WriteMessageN(cn, gdataB, VERSION, NETVERSION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("sent %d byte block request\n", n)
|
||||
return nil
|
||||
}
|
56
uspv/filter.go
Normal file
56
uspv/filter.go
Normal file
@ -0,0 +1,56 @@
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil/bloom"
|
||||
)
|
||||
|
||||
//func (w *Eight3Con) SendFilter() error {
|
||||
|
||||
// filter := bloom.NewFilter(10, 0, 0.001, wire.BloomUpdateAll)
|
||||
|
||||
// // add addresses.
|
||||
// filter.Add(adrBytes1)
|
||||
// filter.Add(adrBytes2)
|
||||
// filter.Add(adrBytes3)
|
||||
// // filter.Add(adrBytes4)
|
||||
// fmt.Printf("hf: %d filter %d bytes: %x\n",
|
||||
// filter.MsgFilterLoad().HashFuncs,
|
||||
// len(filter.MsgFilterLoad().Filter), filter.MsgFilterLoad().Filter)
|
||||
|
||||
// n, err := wire.WriteMessageN(cn,
|
||||
// filter.MsgFilterLoad(), myversion, whichnet)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// log.Printf("sent %d byte filter message\n", n)
|
||||
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func sendFilter(cn net.Conn) error {
|
||||
// adrBytes1, _ := hex.DecodeString(adrHex1)
|
||||
// adrBytes2, _ := hex.DecodeString(adrHex2)
|
||||
// adrBytes3, _ := hex.DecodeString(adrHex3)
|
||||
// ------------------- load a filter
|
||||
// make a new filter. floats ew. hardcode.
|
||||
|
||||
filter := bloom.NewFilter(10, 0, 0.001, wire.BloomUpdateNone)
|
||||
|
||||
// add addresses.
|
||||
// filter.Add(adrBytes1)
|
||||
// filter.Add(adrBytes2)
|
||||
// filter.Add(adrBytes3)
|
||||
// filter.Add(adrBytes4)
|
||||
|
||||
n, err := wire.WriteMessageN(cn,
|
||||
filter.MsgFilterLoad(), VERSION, NETVERSION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("sent %d byte filter message\n", n)
|
||||
return nil
|
||||
}
|
194
uspv/header.go
Normal file
194
uspv/header.go
Normal file
@ -0,0 +1,194 @@
|
||||
/* this is blockchain technology. Well, except without the blocks.
|
||||
Really it's header chain technology.
|
||||
The blocks themselves don't really make a chain. Just the headers do.
|
||||
*/
|
||||
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// blockchain settings. These are kindof bitcoin specific, but not contained in
|
||||
// chaincfg.Params so they'll go here. If you're into the [ANN]altcoin scene,
|
||||
// you may want to paramaterize these constants.
|
||||
const (
|
||||
targetTimespan = time.Hour * 24 * 14
|
||||
targetSpacing = time.Minute * 10
|
||||
epochLength = int64(targetTimespan / targetSpacing)
|
||||
maxDiffAdjust = 4
|
||||
minRetargetTimespan = int64(targetTimespan / maxDiffAdjust)
|
||||
maxRetargetTimespan = int64(targetTimespan * maxDiffAdjust)
|
||||
)
|
||||
|
||||
/* checkProofOfWork verifies the header hashes into something
|
||||
lower than specified by the 4-byte bits field. */
|
||||
func checkProofOfWork(header wire.BlockHeader, p *chaincfg.Params) bool {
|
||||
target := blockchain.CompactToBig(header.Bits)
|
||||
|
||||
// The target must more than 0. Why can you even encode negative...
|
||||
if target.Sign() <= 0 {
|
||||
log.Printf("block target %064x is neagtive(??)\n", target.Bytes())
|
||||
return false
|
||||
}
|
||||
// The target must be less than the maximum allowed (difficulty 1)
|
||||
if target.Cmp(p.PowLimit) > 0 {
|
||||
log.Printf("block target %064x is "+
|
||||
"higher than max of %064x", target, p.PowLimit.Bytes())
|
||||
return false
|
||||
}
|
||||
// The header hash must be less than the claimed target in the header.
|
||||
blockHash := header.BlockSha()
|
||||
hashNum := blockchain.ShaHashToBig(&blockHash)
|
||||
if hashNum.Cmp(target) > 0 {
|
||||
log.Printf("block hash %064x is higher than "+
|
||||
"required target of %064x", hashNum, target)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/* calcDiff returns a bool given two block headers. This bool is
|
||||
true if the correct dificulty adjustment is seen in the "next" header.
|
||||
Only feed it headers n-2016 and n-1, otherwise it will calculate a difficulty
|
||||
when no adjustment should take place, and return false.
|
||||
Note that the epoch is actually 2015 blocks long, which is confusing. */
|
||||
func calcDiffAdjust(start, end wire.BlockHeader, p *chaincfg.Params) uint32 {
|
||||
duration := end.Timestamp.UnixNano() - start.Timestamp.UnixNano()
|
||||
if duration < minRetargetTimespan {
|
||||
log.Printf("whoa there, block %s off-scale high 4X diff adjustment!",
|
||||
end.BlockSha().String())
|
||||
duration = minRetargetTimespan
|
||||
} else if duration > maxRetargetTimespan {
|
||||
log.Printf("Uh-oh! block %s off-scale low 0.25X diff adjustment!\n",
|
||||
end.BlockSha().String())
|
||||
duration = maxRetargetTimespan
|
||||
}
|
||||
|
||||
// calculation of new 32-byte difficulty target
|
||||
// first turn the previous target into a big int
|
||||
prevTarget := blockchain.CompactToBig(start.Bits)
|
||||
// new target is old * duration...
|
||||
newTarget := new(big.Int).Mul(prevTarget, big.NewInt(duration))
|
||||
// divided by 2 weeks
|
||||
newTarget.Div(newTarget, big.NewInt(int64(targetTimespan)))
|
||||
|
||||
// clip again if above minimum target (too easy)
|
||||
if newTarget.Cmp(p.PowLimit) > 0 {
|
||||
newTarget.Set(p.PowLimit)
|
||||
}
|
||||
|
||||
// calculate and return 4-byte 'bits' difficulty from 32-byte target
|
||||
return blockchain.BigToCompact(newTarget)
|
||||
}
|
||||
|
||||
func CheckHeader(r io.ReadSeeker, height int64, p *chaincfg.Params) bool {
|
||||
var err error
|
||||
var cur, prev, epochStart wire.BlockHeader
|
||||
// don't try to verfy the genesis block. That way madness lies.
|
||||
if height == 0 {
|
||||
return true
|
||||
}
|
||||
// initial load of headers
|
||||
// load epochstart, previous and current.
|
||||
// get the header from the epoch start, up to 2016 blocks ago
|
||||
_, err = r.Seek(80*(height-(height%epochLength)), os.SEEK_SET)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
return false
|
||||
}
|
||||
err = epochStart.Deserialize(r)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
return false
|
||||
}
|
||||
log.Printf("start epoch at height %d ", height-(height%epochLength))
|
||||
|
||||
// seek to n-1 header
|
||||
_, err = r.Seek(80*(height-1), os.SEEK_SET)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
return false
|
||||
}
|
||||
// read in n-1
|
||||
err = prev.Deserialize(r)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
return false
|
||||
}
|
||||
// seek to curHeight header and read in
|
||||
_, err = r.Seek(80*(height), os.SEEK_SET)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
return false
|
||||
}
|
||||
err = cur.Deserialize(r)
|
||||
if err != nil {
|
||||
log.Printf(err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// get hash of n-1 header
|
||||
prevHash := prev.BlockSha()
|
||||
// check if headers link together. That whole 'blockchain' thing.
|
||||
if prevHash.IsEqual(&cur.PrevBlock) == false {
|
||||
log.Printf("Headers %d and %d don't link.\n",
|
||||
height-1, height)
|
||||
log.Printf("%s - %s",
|
||||
prev.BlockSha().String(), cur.BlockSha().String())
|
||||
return false
|
||||
}
|
||||
rightBits := epochStart.Bits // normal, no adjustment; Dn = Dn-1
|
||||
// see if we're on a difficulty adjustment block
|
||||
if (height)%epochLength == 0 {
|
||||
// if so, check if difficulty adjustment is valid.
|
||||
// That whole "controlled supply" thing.
|
||||
// calculate diff n based on n-2016 ... n-1
|
||||
rightBits = calcDiffAdjust(epochStart, prev, p)
|
||||
// done with adjustment, save new ephochStart header
|
||||
epochStart = cur
|
||||
log.Printf("Update epoch at height %d", height)
|
||||
} else { // not a new epoch
|
||||
// if on testnet, check for difficulty nerfing
|
||||
if p.ResetMinDifficulty && cur.Timestamp.After(
|
||||
prev.Timestamp.Add(targetSpacing*2)) {
|
||||
// fmt.Printf("nerf %d ", curHeight)
|
||||
rightBits = p.PowLimitBits // difficulty 1
|
||||
}
|
||||
if cur.Bits != rightBits {
|
||||
log.Printf("Block %d %s incorrect difficuly. Read %x, expect %x\n",
|
||||
height, cur.BlockSha().String(), cur.Bits, rightBits)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check if there's a valid proof of work. That whole "Bitcoin" thing.
|
||||
if !checkProofOfWork(cur, p) {
|
||||
log.Printf("Block %d Bad proof of work.\n", height)
|
||||
return false
|
||||
}
|
||||
|
||||
return true // it must have worked if there's no errors and got to the end.
|
||||
}
|
||||
|
||||
/* checkrange verifies a range of headers. it checks their proof of work,
|
||||
difficulty adjustments, and that they all link in to each other properly.
|
||||
This is the only blockchain technology in the whole code base.
|
||||
Returns false if anything bad happens. Returns true if the range checks
|
||||
out with no errors. */
|
||||
func CheckRange(r io.ReadSeeker, first, last int64, p *chaincfg.Params) bool {
|
||||
for i := first; i <= last; i++ {
|
||||
if !CheckHeader(r, i, p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true // all good.
|
||||
}
|
169
uspv/mblock.go
Normal file
169
uspv/mblock.go
Normal file
@ -0,0 +1,169 @@
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
func MakeMerkleParent(left *wire.ShaHash, right *wire.ShaHash) *wire.ShaHash {
|
||||
// this can screw things up; CVE-2012-2459
|
||||
if left != nil && right != nil && left.IsEqual(right) {
|
||||
fmt.Printf("DUP HASH CRASH")
|
||||
return nil
|
||||
}
|
||||
// if left chils is nil, output nil. Shouldn't need this?
|
||||
if left == nil {
|
||||
fmt.Printf("L CRASH")
|
||||
return nil
|
||||
}
|
||||
// if right is nil, has left with itself
|
||||
if right == nil {
|
||||
right = left
|
||||
}
|
||||
|
||||
// Concatenate the left and right nodes
|
||||
var sha [wire.HashSize * 2]byte
|
||||
copy(sha[:wire.HashSize], left[:])
|
||||
copy(sha[wire.HashSize:], right[:])
|
||||
|
||||
newSha := wire.DoubleSha256SH(sha[:])
|
||||
return &newSha
|
||||
}
|
||||
|
||||
type merkleNode struct {
|
||||
p uint32 // position in the binary tree
|
||||
h *wire.ShaHash // hash
|
||||
}
|
||||
|
||||
// given n merkle leaves, how deep is the tree?
|
||||
// iterate shifting left until greater than n
|
||||
func treeDepth(n uint32) (e uint8) {
|
||||
for ; (1 << e) < n; e++ {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// smallest power of 2 that can contain n
|
||||
func nextPowerOfTwo(n uint32) uint32 {
|
||||
return 1 << treeDepth(n) // 2^exponent
|
||||
}
|
||||
|
||||
// check if a node is populated based on node position and size of tree
|
||||
func inDeadZone(pos, size uint32) bool {
|
||||
msb := nextPowerOfTwo(size)
|
||||
last := size - 1 // last valid position is 1 less than size
|
||||
if pos > (msb<<1)-2 { // greater than root; not even in the tree
|
||||
fmt.Printf(" ?? greater than root ")
|
||||
return true
|
||||
}
|
||||
h := msb
|
||||
for pos >= h {
|
||||
h = h>>1 | msb
|
||||
last = last>>1 | msb
|
||||
}
|
||||
return pos > last
|
||||
}
|
||||
|
||||
// take in a merkle block, parse through it, and return the
|
||||
// txids that they're trying to tell us about. If there's any problem
|
||||
// return an error.
|
||||
// doing it with a stack instead of recursion. Because...
|
||||
// OK I don't know why I'm just not in to recursion OK?
|
||||
func checkMBlock(m *wire.MsgMerkleBlock) ([]*wire.ShaHash, error) {
|
||||
if m.Transactions == 0 {
|
||||
return nil, fmt.Errorf("No transactions in merkleblock")
|
||||
}
|
||||
if len(m.Flags) == 0 {
|
||||
return nil, fmt.Errorf("No flag bits")
|
||||
}
|
||||
var s []merkleNode // the stack
|
||||
var r []*wire.ShaHash // slice to return; txids we care about
|
||||
|
||||
// set initial position to root of merkle tree
|
||||
msb := nextPowerOfTwo(m.Transactions) // most significant bit possible
|
||||
pos := (msb << 1) - 2 // current position in tree
|
||||
|
||||
var i uint8 // position in the current flag byte
|
||||
var tip int
|
||||
// main loop
|
||||
for {
|
||||
tip = len(s) - 1 // slice position of stack tip
|
||||
// First check if stack operations can be performed
|
||||
// is stack one filled item? that's complete.
|
||||
if tip == 0 && s[0].h != nil {
|
||||
if s[0].h.IsEqual(&m.Header.MerkleRoot) {
|
||||
return r, nil
|
||||
}
|
||||
return nil, fmt.Errorf("computed root %s but expect %s\n",
|
||||
s[0].h.String(), m.Header.MerkleRoot.String())
|
||||
}
|
||||
// is current position in the tree's dead zone? partial parent
|
||||
if inDeadZone(pos, m.Transactions) {
|
||||
// create merkle parent from single side (left)
|
||||
s[tip-1].h = MakeMerkleParent(s[tip].h, nil)
|
||||
s = s[:tip] // remove 1 from stack
|
||||
pos = s[tip-1].p | 1 // move position to parent's sibling
|
||||
continue
|
||||
}
|
||||
// does stack have 3+ items? and are last 2 items filled?
|
||||
if tip > 1 && s[tip-1].h != nil && s[tip].h != nil {
|
||||
//fmt.Printf("nodes %d and %d combine into %d\n",
|
||||
// s[tip-1].p, s[tip].p, s[tip-2].p)
|
||||
// combine two filled nodes into parent node
|
||||
s[tip-2].h = MakeMerkleParent(s[tip-1].h, s[tip].h)
|
||||
// remove children
|
||||
s = s[:tip-1]
|
||||
// move position to parent's sibling
|
||||
pos = s[tip-2].p | 1
|
||||
continue
|
||||
}
|
||||
|
||||
// no stack ops to perform, so make new node from message hashes
|
||||
if len(m.Hashes) == 0 {
|
||||
return nil, fmt.Errorf("Ran out of hashes at position %d.", pos)
|
||||
}
|
||||
if len(m.Flags) == 0 {
|
||||
return nil, fmt.Errorf("Ran out of flag bits.")
|
||||
}
|
||||
var n merkleNode // make new node
|
||||
n.p = pos // set current position for new node
|
||||
|
||||
if pos&msb != 0 { // upper non-txid hash
|
||||
if m.Flags[0]&(1<<i) == 0 { // flag bit says fill node
|
||||
n.h = m.Hashes[0] // copy hash from message
|
||||
m.Hashes = m.Hashes[1:] // pop off message
|
||||
if pos&1 != 0 { // right side; ascend
|
||||
pos = pos>>1 | msb
|
||||
} else { // left side, go to sibling
|
||||
pos |= 1
|
||||
}
|
||||
} else { // flag bit says skip; put empty on stack and descend
|
||||
pos = (pos ^ msb) << 1 // descend to left
|
||||
}
|
||||
s = append(s, n) // push new node on stack
|
||||
} else { // bottom row txid; flag bit indicates tx of interest
|
||||
if pos >= m.Transactions {
|
||||
// this can't happen because we check deadzone above...
|
||||
return nil, fmt.Errorf("got into an invalid txid node")
|
||||
}
|
||||
n.h = m.Hashes[0] // copy hash from message
|
||||
m.Hashes = m.Hashes[1:] // pop off message
|
||||
if m.Flags[0]&(1<<i) != 0 { //txid of interest
|
||||
r = append(r, n.h)
|
||||
}
|
||||
if pos&1 == 0 { // left side, go to sibling
|
||||
pos |= 1
|
||||
} // if on right side we don't move; stack ops will move next
|
||||
s = append(s, n) // push new node onto the stack
|
||||
}
|
||||
|
||||
// done with pushing onto stack; advance flag bit
|
||||
i++
|
||||
if i == 8 { // move to next byte
|
||||
i = 0
|
||||
m.Flags = m.Flags[1:]
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("ran out of things to do?")
|
||||
}
|
68
uspv/msghandler.go
Normal file
68
uspv/msghandler.go
Normal file
@ -0,0 +1,68 @@
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
func (e3c *SPVCon) incomingMessageHandler() {
|
||||
for {
|
||||
n, xm, _, err := wire.ReadMessageN(e3c.con, e3c.localVersion, e3c.netType)
|
||||
if err != nil {
|
||||
log.Printf("ReadMessageN error. Disconnecting: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
e3c.RBytes += uint64(n)
|
||||
// log.Printf("Got %d byte %s message\n", n, xm.Command())
|
||||
switch m := xm.(type) {
|
||||
case *wire.MsgVersion:
|
||||
log.Printf("Got version message. Agent %s, version %d, at height %d\n",
|
||||
m.UserAgent, m.ProtocolVersion, m.LastBlock)
|
||||
e3c.remoteVersion = uint32(m.ProtocolVersion) // weird cast! bug?
|
||||
case *wire.MsgVerAck:
|
||||
log.Printf("Got verack. Whatever.\n")
|
||||
case *wire.MsgAddr:
|
||||
log.Printf("got %d addresses.\n", len(m.AddrList))
|
||||
case *wire.MsgPing:
|
||||
log.Printf("Got a ping message. We should pong back or they will kick us off.")
|
||||
e3c.PongBack(m.Nonce)
|
||||
case *wire.MsgPong:
|
||||
log.Printf("Got a pong response. OK.\n")
|
||||
case *wire.MsgMerkleBlock:
|
||||
log.Printf("Got merkle block message. Will verify.\n")
|
||||
fmt.Printf("%d flag bytes, %d txs, %d hashes",
|
||||
len(m.Flags), m.Transactions, len(m.Hashes))
|
||||
txids, err := checkMBlock(m)
|
||||
if err != nil {
|
||||
log.Printf("Merkle block error: %s\n", err.Error())
|
||||
return
|
||||
// continue
|
||||
}
|
||||
fmt.Printf(" = got %d txs from block %s\n",
|
||||
len(txids), m.Header.BlockSha().String())
|
||||
// nextReq <- true
|
||||
case *wire.MsgTx:
|
||||
|
||||
log.Printf("Got tx %s\n", m.TxSha().String())
|
||||
default:
|
||||
log.Printf("Got unknown message type %s\n", m.Command())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// this one seems kindof pointless? could get ridf of it and let
|
||||
// functions call WriteMessageN themselves...
|
||||
func (e3c *SPVCon) outgoingMessageHandler() {
|
||||
for {
|
||||
msg := <-e3c.outMsgQueue
|
||||
n, err := wire.WriteMessageN(e3c.con, msg, e3c.localVersion, e3c.netType)
|
||||
if err != nil {
|
||||
log.Printf("Write message error: %s", err.Error())
|
||||
}
|
||||
e3c.WBytes += uint64(n)
|
||||
}
|
||||
return
|
||||
}
|
119
uspv/txstore.go
Normal file
119
uspv/txstore.go
Normal file
@ -0,0 +1,119 @@
|
||||
package uspv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/bloom"
|
||||
)
|
||||
|
||||
type TxStore struct {
|
||||
KnownTxids []wire.ShaHash
|
||||
Utxos []Utxo // stacks on stacks
|
||||
Sum int64 // racks on racks
|
||||
Adrs []MyAdr // endeavouring to acquire capital
|
||||
}
|
||||
|
||||
type Utxo struct { // cash money.
|
||||
// combo of outpoint and txout which has all the info needed to spend
|
||||
Op wire.OutPoint
|
||||
Txo wire.TxOut
|
||||
KeyIdx uint32 // index for private key needed to sign / spend
|
||||
}
|
||||
|
||||
type MyAdr struct { // an address I have the private key for
|
||||
btcutil.Address
|
||||
KeyIdx uint32 // index for private key needed to sign / spend
|
||||
}
|
||||
|
||||
func (t *TxStore) AddAdr(a btcutil.Address, kidx uint32) {
|
||||
var ma MyAdr
|
||||
ma.Address = a
|
||||
ma.KeyIdx = kidx
|
||||
t.Adrs = append(t.Adrs, ma)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
|
||||
if len(t.Adrs) == 0 {
|
||||
return nil, fmt.Errorf("no addresses to filter for")
|
||||
}
|
||||
f := bloom.NewFilter(uint32(len(t.Adrs)), 0, 0.001, wire.BloomUpdateNone)
|
||||
for _, a := range t.Adrs {
|
||||
f.Add(a.ScriptAddress())
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Ingest a tx into wallet, dealing with both gains and losses
|
||||
func (t *TxStore) IngestTx(tx *wire.MsgTx) error {
|
||||
err := t.AbsorbTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.ExpellTx(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Absorb money into wallet from a tx
|
||||
func (t *TxStore) AbsorbTx(tx *wire.MsgTx) error {
|
||||
if tx == nil {
|
||||
return fmt.Errorf("Tried to add nil tx")
|
||||
}
|
||||
var hits uint32
|
||||
var acq int64
|
||||
// check if any of the tx's outputs match my adrs
|
||||
for i, out := range tx.TxOut { // in each output of tx
|
||||
for _, a := range t.Adrs { // compare to each adr we have
|
||||
// more correct would be to check for full script
|
||||
// contains could have false positive? (p2sh/p2pkh same hash ..?)
|
||||
if bytes.Contains(out.PkScript, a.ScriptAddress()) { // hit
|
||||
hits++
|
||||
acq += out.Value
|
||||
var newu Utxo
|
||||
newu.KeyIdx = a.KeyIdx
|
||||
newu.Txo = *out
|
||||
|
||||
var newop wire.OutPoint
|
||||
newop.Hash = tx.TxSha()
|
||||
newop.Index = uint32(i)
|
||||
newu.Op = newop
|
||||
|
||||
t.Utxos = append(t.Utxos)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("%d hits, acquired %d", hits, acq)
|
||||
t.Sum += acq
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expell money from wallet due to a tx
|
||||
func (t *TxStore) ExpellTx(tx *wire.MsgTx) error {
|
||||
if tx == nil {
|
||||
return fmt.Errorf("Tried to add nil tx")
|
||||
}
|
||||
var hits uint32
|
||||
var loss int64
|
||||
|
||||
for _, in := range tx.TxIn {
|
||||
for i, myutxo := range t.Utxos {
|
||||
if myutxo.Op == in.PreviousOutPoint {
|
||||
hits++
|
||||
loss += myutxo.Txo.Value
|
||||
// delete from my utxo set
|
||||
t.Utxos = append(t.Utxos[:i], t.Utxos[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("%d hits, lost %d", hits, loss)
|
||||
t.Sum -= loss
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user