From 1fbd3275024d19ff85b56c413569fa42affa63e6 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 17 Jul 2019 21:39:06 -0800 Subject: [PATCH 001/124] refactor(factom): Allow ComputeKeyMR/Hash() to modify ObjectCount or ChainID --- factom/bytes.go | 8 ++++++++ factom/eblock.go | 40 ++++++++++++++++++++++++---------------- factom/entry.go | 17 +++++++++++------ factom/entry_test.go | 3 --- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/factom/bytes.go b/factom/bytes.go index a0ee077..d11151e 100644 --- a/factom/bytes.go +++ b/factom/bytes.go @@ -69,6 +69,14 @@ func (b *Bytes32) Set(hexStr string) error { return nil } +// NewBytesFromString makes a new Bytes object with the hex encoded string data +// contained in s. +func NewBytesFromString(s string) Bytes { + var b Bytes + b.Set(s) + return b +} + // Set decodes a string with hex encoded data. func (b *Bytes) Set(hexStr string) error { *b = make(Bytes, hex.DecodedLen(len(hexStr))) diff --git a/factom/eblock.go b/factom/eblock.go index 378cf4a..8d3165f 100644 --- a/factom/eblock.go +++ b/factom/eblock.go @@ -62,7 +62,6 @@ func (eb EBlock) IsPopulated() bool { eb.PrevKeyMR != nil && eb.PrevFullHash != nil && eb.BodyMR != nil && - eb.Height > 0 && eb.ObjectCount > 1 } @@ -212,6 +211,7 @@ const ( // UnmarshalBinary unmarshals raw entry block data. // // Header +// // [ChainID (Bytes32)] + // [BodyMR (Bytes32)] + // [PrevKeyMR (Bytes32)] + @@ -221,7 +221,8 @@ const ( // [Object Count (uint32 BE)] // // Body -// [Object 0 (Bytes32)] + // entry hash or minute marker +// +// [Object 0 (Bytes32)] // entry hash or minute marker + // ... + // [Object N (Bytes32)] // @@ -233,11 +234,13 @@ func (eb *EBlock) UnmarshalBinary(data []byte) error { if len(data) > EBlockMaxTotalLen { return fmt.Errorf("invalid length") } + + // When the eb.ChainID is already populated, just reuse the data. if eb.ChainID == nil { eb.ChainID = new(Bytes32) + copy(eb.ChainID[:], data[:len(eb.ChainID)]) } - eb.ChainID = eb.ChainID - i := copy(eb.ChainID[:], data[:len(eb.ChainID)]) + i := len(eb.ChainID) eb.BodyMR = new(Bytes32) i += copy(eb.BodyMR[:], data[i:i+len(eb.BodyMR)]) eb.PrevKeyMR = new(Bytes32) @@ -261,11 +264,12 @@ func (eb *EBlock) UnmarshalBinary(data []byte) error { for oi := range objects { obj := &objects[len(objects)-1-oi] // Reverse object order i += copy(obj[:], data[i:i+len(obj)]) - if bytes.Compare(obj[:], maxMinute[:]) <= 0 { - numMins++ + if bytes.Compare(obj[:], maxMinute[:]) <= 0 { // if obj <= maxMinute + numMins++ // obj is a minute marker } } - if bytes.Compare(objects[0][:], maxMinute[:]) > 0 { + // The last object (which is now index 0) must be a minute marker. + if bytes.Compare(objects[0][:], maxMinute[:]) > 0 { // if obj > maxMinute return fmt.Errorf("invalid minute marker") } @@ -291,9 +295,6 @@ func (eb *EBlock) UnmarshalBinary(data []byte) error { } func (eb *EBlock) MarshalBinaryHeader() ([]byte, error) { - if !eb.IsPopulated() { - return nil, fmt.Errorf("not populated") - } data := make([]byte, eb.MarshalBinaryLen()) i := copy(data, eb.ChainID[:]) i += copy(data[i:], eb.BodyMR[:]) @@ -303,6 +304,7 @@ func (eb *EBlock) MarshalBinaryHeader() ([]byte, error) { i += 4 binary.BigEndian.PutUint32(data[i:], eb.Height) i += 4 + eb.ObjectCount = eb.CountObjects() binary.BigEndian.PutUint32(data[i:], eb.ObjectCount) i += 4 return data, nil @@ -325,6 +327,9 @@ func (eb *EBlock) MarshalBinary() ([]byte, error) { } func (eb *EBlock) Objects() ([]Bytes32, error) { + if eb.ObjectCount == 0 { + eb.ObjectCount = eb.CountObjects() + } objects := make([]Bytes32, eb.ObjectCount) var lastMin, oi int lastMin = int(eb.Entries[0].Timestamp.Sub(eb.Timestamp).Minutes()) @@ -345,12 +350,15 @@ func (eb *EBlock) Objects() ([]Bytes32, error) { lastE := eb.Entries[len(eb.Entries)-1] lastMin = int(lastE.Timestamp.Sub(eb.Timestamp).Minutes()) objects[oi][31] = byte(lastMin) - oi++ return objects, nil } func (eb *EBlock) CountObjects() uint32 { - var numMins, lastMin int + if len(eb.Entries) == 0 { + panic("no entries") + } + var lastMin int + numMins := 1 // There is always at least one minute marker. for _, e := range eb.Entries { min := int(e.Timestamp.Sub(eb.Timestamp).Minutes()) if min > lastMin { @@ -368,7 +376,7 @@ func (eb *EBlock) MarshalBinaryLen() int { return EBlockHeaderLen + int(eb.ObjectCount)*len(Bytes32{}) } -func (eb EBlock) ComputeBodyMR() (Bytes32, error) { +func (eb *EBlock) ComputeBodyMR() (Bytes32, error) { var bodyMR Bytes32 objects, err := eb.Objects() if err != nil { @@ -389,7 +397,7 @@ func (eb EBlock) ComputeBodyMR() (Bytes32, error) { return bodyMR, nil } -func (eb EBlock) ComputeFullHash() (Bytes32, error) { +func (eb *EBlock) ComputeFullHash() (Bytes32, error) { data, err := eb.MarshalBinary() if err != nil { return Bytes32{}, err @@ -397,7 +405,7 @@ func (eb EBlock) ComputeFullHash() (Bytes32, error) { return sha256.Sum256(data), nil } -func (eb EBlock) ComputeHeaderHash() (Bytes32, error) { +func (eb *EBlock) ComputeHeaderHash() (Bytes32, error) { header, err := eb.MarshalBinaryHeader() if err != nil { return Bytes32{}, err @@ -405,7 +413,7 @@ func (eb EBlock) ComputeHeaderHash() (Bytes32, error) { return sha256.Sum256(header[:EBlockHeaderLen]), nil } -func (eb EBlock) ComputeKeyMR() (Bytes32, error) { +func (eb *EBlock) ComputeKeyMR() (Bytes32, error) { headerHash, err := eb.ComputeHeaderHash() if err != nil { return Bytes32{}, err diff --git a/factom/entry.go b/factom/entry.go index 4f4543b..894e1a7 100644 --- a/factom/entry.go +++ b/factom/entry.go @@ -401,13 +401,19 @@ func (e *Entry) UnmarshalBinary(data []byte) error { if data[0] != 0x00 { return fmt.Errorf("invalid version byte") } - chainID := data[1:33] + i := 1 + // When the e.ChainID is already populated, just reuse the data. + if e.ChainID == nil { + e.ChainID = new(Bytes32) + copy(e.ChainID[:], data[i:i+len(e.ChainID)]) + } + i += len(e.ChainID) extIDTotalLen := int(binary.BigEndian.Uint16(data[33:35])) if extIDTotalLen == 1 || EntryHeaderLen+extIDTotalLen > len(data) { return fmt.Errorf("invalid ExtIDs length") } - extIDs := []Bytes{} + e.ExtIDs = []Bytes{} pos := EntryHeaderLen for pos < EntryHeaderLen+extIDTotalLen { extIDLen := int(binary.BigEndian.Uint16(data[pos : pos+2])) @@ -415,18 +421,17 @@ func (e *Entry) UnmarshalBinary(data []byte) error { return fmt.Errorf("error parsing ExtIDs") } pos += 2 - extIDs = append(extIDs, Bytes(data[pos:pos+extIDLen])) + e.ExtIDs = append(e.ExtIDs, Bytes(data[pos:pos+extIDLen])) pos += extIDLen } + e.Content = data[pos:] - e.ExtIDs = extIDs - e.ChainID = NewBytes32(chainID) return nil } // ComputeHash returns the Entry's hash as computed by hashing the binary // representation of the Entry. -func (e Entry) ComputeHash() (Bytes32, error) { +func (e *Entry) ComputeHash() (Bytes32, error) { data, err := e.MarshalBinary() return EntryHash(data), err } diff --git a/factom/entry_test.go b/factom/entry_test.go index f98bb27..b01077f 100644 --- a/factom/entry_test.go +++ b/factom/entry_test.go @@ -122,9 +122,6 @@ func TestEntry(t *testing.T) { assert.Equal(*test.Hash, hash) } else { require.EqualError(err, test.Error) - assert.Nil(e.ChainID) - assert.Nil(e.Content) - assert.Nil(e.ExtIDs) } }) } From ba2857840683b004ebe2f18ac75d5539508c2abb Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 18 Jul 2019 16:49:45 -0800 Subject: [PATCH 002/124] fix(factom,engine): Fix EBlock{}.CountObject and set accurate testnet start height --- engine/engine.go | 2 +- factom/dblock_eblock_entry_test.go | 16 +++++++++------- factom/eblock.go | 22 +++++++++++++++------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index bd431f8..1c87da1 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -102,7 +102,7 @@ func engine(stop <-chan struct{}, done chan struct{}) { setSyncHeight(uint32(flag.StartScanHeight - 1)) } else if syncHeight == 0 { // else if the syncHeight has not been set... const mainnetStart = 163180 - const testnetStart = 60000 + const testnetStart = 60783 // This is a hacky, unreliable way to determine what network we // are on. This needs to be replaced with using the actually // Network ID. diff --git a/factom/dblock_eblock_entry_test.go b/factom/dblock_eblock_entry_test.go index c5cd2a1..51355e6 100644 --- a/factom/dblock_eblock_entry_test.go +++ b/factom/dblock_eblock_entry_test.go @@ -148,13 +148,15 @@ func TestDataStructures(t *testing.T) { c.FactomdServer = courtesyNode ebs, err := eb.GetAllPrev(c) - assert.NoError(err) - assert.Len(ebs, 6) - assert.True(ebs[0].IsFirst()) - first := ebs[0].Prev() - assert.Equal(first.KeyMR, ebs[0].KeyMR, - "Prev() should return a copy of itself if it is first") - assert.Equal(eb.KeyMR, ebs[len(ebs)-1].KeyMR) + var first EBlock + if assert.NoError(err) { + assert.Len(ebs, 6) + assert.True(ebs[0].IsFirst()) + first = ebs[0].Prev() + assert.Equal(first.KeyMR, ebs[0].KeyMR, + "Prev() should return a copy of itself if it is first") + assert.Equal(eb.KeyMR, ebs[len(ebs)-1].KeyMR) + } // Fetch the chain head EBlock via the ChainID. // First use an invalid ChainID and an invalid URL. diff --git a/factom/eblock.go b/factom/eblock.go index 8d3165f..9d8e7f5 100644 --- a/factom/eblock.go +++ b/factom/eblock.go @@ -113,7 +113,7 @@ func (eb *EBlock) Get(c *Client) error { return err } if *eb.KeyMR != keyMR { - return fmt.Errorf("invalid key merkle root") + return fmt.Errorf("invalid KeyMR") } return nil } @@ -353,17 +353,25 @@ func (eb *EBlock) Objects() ([]Bytes32, error) { return objects, nil } -func (eb *EBlock) CountObjects() uint32 { +func (eb EBlock) CountObjects() uint32 { if len(eb.Entries) == 0 { panic("no entries") } - var lastMin int - numMins := 1 // There is always at least one minute marker. + mins := make([]bool, 10) + var numMins int for _, e := range eb.Entries { - min := int(e.Timestamp.Sub(eb.Timestamp).Minutes()) - if min > lastMin { + minute := e.Timestamp.Sub(eb.Timestamp) + // Except for zero, timestamps get rounded down including those + // that fall exactly on a minute. + if minute > 0 && minute.Truncate(time.Minute) == minute { + minute -= time.Minute + } else { + minute = minute.Truncate(time.Minute) + } + mi := int(minute.Minutes()) + if !mins[mi] { + mins[mi] = true numMins++ - lastMin = min } } return uint32(len(eb.Entries) + numMins) From d1e83a2dad7c9b0efa4582fcd428f3772726cdc7 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 17 Jul 2019 21:40:35 -0800 Subject: [PATCH 003/124] feat(db): Add db pkg with Insert/Select primitives for EBlocks, Entries, Addresses --- db/address.go | 61 ++++++++++++++++++ db/connpool.go | 68 ++++++++++++++++++++ db/db.go | 81 +++++++++++++++++++++++ db/db_test.go | 170 +++++++++++++++++++++++++++++++++++++++++++++++++ db/eblock.go | 124 ++++++++++++++++++++++++++++++++++++ db/entry.go | 90 ++++++++++++++++++++++++++ db/metadata.go | 58 +++++++++++++++++ db/schema.go | 136 +++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 8 +++ 10 files changed, 797 insertions(+) create mode 100644 db/address.go create mode 100644 db/connpool.go create mode 100644 db/db.go create mode 100644 db/db_test.go create mode 100644 db/eblock.go create mode 100644 db/entry.go create mode 100644 db/metadata.go create mode 100644 db/schema.go diff --git a/db/address.go b/db/address.go new file mode 100644 index 0000000..f28c4d4 --- /dev/null +++ b/db/address.go @@ -0,0 +1,61 @@ +package db + +import ( + "fmt" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/factom" +) + +// AddressAdd adds add to the balance of adr, if it exists, otherwise it +// inserts the address with balance set to add. +func AddressAdd(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) error { + stmt := conn.Prep(`INSERT INTO addresses +(address, balance) VALUES (?, ?) +ON CONFLICT(address) DO +UPDATE SET balance = balance + ?;`) + stmt.BindBytes(1, adr[:]) + stmt.BindInt64(2, int64(add)) + stmt.BindInt64(3, int64(add)) + _, err := stmt.Step() + return err +} + +// AddressSub subtracts sub from the balance of adr, only if this does not +// cause balance to be < 0, otherwise an error is returned. +func AddressSub(conn *sqlite.Conn, adr *factom.FAAddress, sub uint64) error { + stmt := conn.Prep(`UPDATE addresses +SET balance = balance - ? +WHERE address = ?;`) + stmt.BindInt64(1, int64(sub)) + stmt.BindBytes(2, adr[:]) + if _, err := stmt.Step(); err != nil { + return err + } + if conn.Changes() == 0 { + return fmt.Errorf("CHECK constraint failed: insufficient balance") + } + return nil +} + +// SelectAddress returns the id and balance for the given adr. +func SelectAddress(conn *sqlite.Conn, adr *factom.FAAddress) (int64, uint64, error) { + stmt := conn.Prep(`SELECT id, balance FROM addresses WHERE address = ?;`) + stmt.BindBytes(1, adr[:]) + if _, err := stmt.Step(); err != nil { + return 0, 0, err + } + return stmt.ColumnInt64(0), uint64(stmt.ColumnInt64(1)), nil +} + +func InsertAddressTransaction(conn *sqlite.Conn, + adrID int64, entryID int64, to bool) error { + stmt := conn.Prep(`INSERT INTO address_transactions +(address_id, entry_id, sent_to) VALUES +(?, ?, ?)`) + stmt.BindInt64(1, adrID) + stmt.BindInt64(2, entryID) + stmt.BindBool(3, to) + _, err := stmt.Step() + return err +} diff --git a/db/connpool.go b/db/connpool.go new file mode 100644 index 0000000..359b383 --- /dev/null +++ b/db/connpool.go @@ -0,0 +1,68 @@ +package db + +import ( + "fmt" + + "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" + "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/flag" +) + +// ConnPool combines a READWRITE sqlite.Conn and a READ_ONLY sqlitex.Pool with +// the corresponding ChainID. Only a single thread may use the Read/Write Conn +// at a time. Multi-threaded readers must Pool.Get a read-only Conn from the +// and must Pool.Put the Conn back when finished. +type ConnPool struct { + ChainID *factom.Bytes32 + *sqlite.Conn // Read/Write + *sqlitex.Pool // Read Only Pool +} + +// Open a new ConnPool for the given chainID within flag.DBPath. Validate or +// apply chainDBSchema. +func Open(chainID *factom.Bytes32) (ConnPool, error) { + cp, err := open(chainID.String()+dbFileExtension, chainDBSchema) + if err != nil { + return cp, err + } + cp.ChainID = chainID + return cp, nil +} + +// open a READWRITE Conn and a READ_ONLY Pool for flag.DBPath + "/" + fname. +// Validate or apply schema. +func open(fname, schema string) (ConnPool, error) { + const baseFlags = sqlite.SQLITE_OPEN_WAL | + sqlite.SQLITE_OPEN_URI | + sqlite.SQLITE_OPEN_NOMUTEX + var cp ConnPool + var err error + path := flag.DBPath + "/" + fname + flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE + cp.Conn, err = sqlite.OpenConn(path, flags) + if err != nil { + return cp, fmt.Errorf("sqlite.OpenConn(%q, %x): %v", + path, flags, err) + } + if err := validateOrApplySchema(cp.Conn, schema); err != nil { + return cp, err + } + flags = baseFlags | sqlite.SQLITE_OPEN_READONLY + cp.Pool, err = sqlitex.Open(path, flags, PoolSize) + if err != nil { + return cp, fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", + path, flags, PoolSize, err) + } + return cp, nil +} + +// Close the Conn and Pool. Log any errors. +func (cp ConnPool) Close() { + if err := cp.Conn.Close(); err != nil { + log.Errorf("%v: cp.Conn.Close(): %v", cp.ChainID, err) + } + if err := cp.Pool.Close(); err != nil { + log.Errorf("%v: cp.Pool.Close(): %v", cp.ChainID, err) + } +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..4c01681 --- /dev/null +++ b/db/db.go @@ -0,0 +1,81 @@ +package db + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/flag" + _log "github.com/Factom-Asset-Tokens/fatd/log" +) + +var ( + log _log.Log +) + +const ( + dbDriver = "sqlite3" + dbFileExtension = ".sqlite3" + dbFileNameLen = len(factom.Bytes32{})*2 + len(dbFileExtension) + + PoolSize = 10 +) + +func OpenAll() (cps []ConnPool, err error) { + log = _log.New("db") + // Try to create the database directory in case it doesn't already + // exist. + if err := os.Mkdir(flag.DBPath, 0755); err != nil { + if !os.IsExist(err) { + return nil, fmt.Errorf("os.Mkdir(%#v): %v", flag.DBPath, err) + } + log.Debug("Using existing database directory...") + } + + defer func() { + if err != nil { + for _, cp := range cps { + cp.Close() + } + cps = nil + } + }() + + // Scan through all files within the database directory. Ignore invalid + // file names. + files, err := ioutil.ReadDir(flag.DBPath) + if err != nil { + return cps, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) + } + cps = make([]ConnPool, 0, len(files)) + for _, f := range files { + fname := f.Name() + chainID, err := fnameToChainID(fname) + if err != nil { + log.Debug(err) + continue + } + log.Debugf("Loading chain: %v", chainID) + cp, err := open(fname, chainDBSchema) + if err != nil { + return cps, err + } + cp.ChainID = chainID + cps = append(cps, cp) + } + return cps, nil +} +func fnameToChainID(fname string) (*factom.Bytes32, error) { + invalidFNameErr := fmt.Errorf("invalid filename: %v", fname) + if len(fname) != dbFileNameLen || + fname[dbFileNameLen-len(dbFileExtension):dbFileNameLen] != + dbFileExtension { + return nil, invalidFNameErr + } + chainID := factom.NewBytes32FromString(fname[0:64]) + if chainID == nil { + return nil, invalidFNameErr + } + return chainID, nil +} diff --git a/db/db_test.go b/db/db_test.go new file mode 100644 index 0000000..795b281 --- /dev/null +++ b/db/db_test.go @@ -0,0 +1,170 @@ +package db + +import ( + "fmt" + "io/ioutil" + "math/rand" + "os" + "testing" + "time" + + "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDB(t *testing.T) { + var err error + flag.DBPath, err = ioutil.TempDir(os.TempDir(), "fatd.db-test") + defer func() { + if err := os.RemoveAll(flag.DBPath); err != nil { + fmt.Println("failed to remove temp dir:", err) + } + }() + require.NoError(t, err) + t.Run("Open", func(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + chains, err := OpenAll() + assert.Empty(chains) + require.NoError(err) + eblocks := genChain() + cp, err := Open(eblocks[0].ChainID) + require.NoError(err) + defer cp.Close() + var dbKeyMR factom.Bytes32 + eID := int64(1) + for _, eb := range eblocks { + require.NoError(InsertEBlock(cp.Conn, eb, &dbKeyMR)) + dbKeyMR[0]++ + for _, e := range eb.Entries { + id, err := InsertEntry(cp.Conn, e, eb.Sequence) + require.NoError(err) + assert.Equal(id, eID) + eID++ + } + } + // Ensure only EBlocks with sequential KeyMRs and Sequence + // numbers can be inserted. + eb := eblocks[5] + eb.Sequence = 100 + assert.EqualError(InsertEBlock(cp.Conn, eb, &dbKeyMR), + "invalid EBlock{}.PrevKeyMR") + eb = eblocks[5] + eb.PrevKeyMR = new(factom.Bytes32) + assert.EqualError(InsertEBlock(cp.Conn, eb, &dbKeyMR), + "invalid EBlock{}.PrevKeyMR") + + eID = 1 + for i, eb := range eblocks { + loadedEB, err := SelectEBlockBySequence(cp.Conn, uint32(i)) + require.NoError(err) + assert.Equal(loadedEB.KeyMR, eb.KeyMR) + for _, e := range eb.Entries { + loadByIDE, validI, err := SelectEntryByID(cp.Conn, eID) + eID++ + loadByHashE, validH, err := SelectEntryByHash( + cp.Conn, e.Hash) + assert.Equal(loadByIDE, loadByHashE) + assert.NoError(err) + assert.False(validI) + assert.False(validH) + assert.Equal(*loadByIDE.Hash, *e.Hash) + assert.Equal(loadByIDE.ExtIDs, e.ExtIDs) + assert.Equal(loadByIDE.Content, e.Content) + } + } + }) +} + +var entryCount int + +func genNewEntry(chainID *factom.Bytes32) factom.Entry { + extID := []byte(fmt.Sprintf("%v", entryCount)) + entryCount++ + data := []byte(fmt.Sprintf("%v", time.Now())) + e := factom.Entry{ChainID: chainID, ExtIDs: []factom.Bytes{extID}, Content: data} + hash, err := e.ComputeHash() + if err != nil { + panic(err) + } + e.Hash = &hash + return e +} + +func genChain() []factom.EBlock { + eblocks := make([]factom.EBlock, 6) + eb := &eblocks[0] + height := uint32(10000) + timestamp := time.Now().Add(-7 * 24 * time.Hour).Round(time.Minute) + eb.Timestamp = timestamp + eb.Height = 10000 + eb.PrevKeyMR = new(factom.Bytes32) + eb.PrevFullHash = new(factom.Bytes32) + eb.Entries = []factom.Entry{genNewEntry(nil)} + eb.Entries[0].Timestamp = timestamp.Add(time.Duration(rand.Intn(10)) * time.Minute) + chainID := eb.Entries[0].ChainID + eb.ChainID = chainID + + bodyMR, err := eb.ComputeBodyMR() + if err != nil { + panic(err) + } + eb.BodyMR = &bodyMR + + keyMR, err := eb.ComputeKeyMR() + if err != nil { + panic(err) + } + eb.KeyMR = &keyMR + + fullHash, err := eb.ComputeFullHash() + if err != nil { + panic(err) + } + prevKeyMR := &keyMR + prevFullHash := &fullHash + for i := range eblocks[1:] { + eb := &eblocks[i+1] + eb.Sequence = uint32(i + 1) + eb.ChainID = chainID + numBlocks := uint32(rand.Intn(10) + 1) + height += numBlocks + timestamp = timestamp.Add(time.Duration(numBlocks) * 10 * time.Minute) + eb.Timestamp = timestamp + eb.Height = height + eb.PrevKeyMR = prevKeyMR + eb.PrevFullHash = prevFullHash + eb.Entries = make([]factom.Entry, 2) + for i := range eb.Entries { + e := genNewEntry(chainID) + e.Timestamp = eb.Timestamp.Add( + time.Duration(rand.Intn(10)) * time.Minute) + eb.Entries[i] = e + } + bodyMR, err := eb.ComputeBodyMR() + if err != nil { + panic(err) + } + eb.BodyMR = &bodyMR + + keyMR, err := eb.ComputeKeyMR() + if err != nil { + panic(err) + } + eb.KeyMR = &keyMR + prevKeyMR = &keyMR + + fullHash, err := eb.ComputeFullHash() + if err != nil { + panic(err) + } + prevFullHash = &fullHash + } + return eblocks +} + +func init() { + rand.Seed(100) +} diff --git a/db/eblock.go b/db/eblock.go new file mode 100644 index 0000000..2d9a194 --- /dev/null +++ b/db/eblock.go @@ -0,0 +1,124 @@ +package db + +import ( + "fmt" + "time" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/factom" +) + +// InsertEBlock inserts eb only if it is the next factom.EBlock in the +// sequence. +func InsertEBlock(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) error { + // Ensure that this is the next EBlock. + prevKeyMR, err := SelectKeyMR(conn, eb.Sequence-1) + if *eb.PrevKeyMR != prevKeyMR { + return fmt.Errorf("invalid EBlock{}.PrevKeyMR") + } + + var data []byte + data, err = eb.MarshalBinary() + if err != nil { + return fmt.Errorf("factom.EBlock{}.MarshalBinary(): %v", err) + } + stmt := conn.Prep(`INSERT INTO eblocks + (seq, key_mr, db_height, db_key_mr, timestamp, data) + VALUES (?, ?, ?, ?, ?, ?);`) + stmt.BindInt64(1, int64(eb.Sequence)) + stmt.BindBytes(2, eb.KeyMR[:]) + stmt.BindInt64(3, int64(eb.Height)) + stmt.BindBytes(4, dbKeyMR[:]) + stmt.BindInt64(5, int64(eb.Timestamp.Unix())) + stmt.BindBytes(6, data) + + _, err = stmt.Step() + return err +} + +// SelectEBlockWhere is a SQL fragment that must be appended with the condition +// of a WHERE clause and a final semi-colon. +const SelectEBlockWhere = `SELECT key_mr, data, timestamp FROM eblocks WHERE ` + +// SelectEBlock uses stmt to populate and return a new factom.EBlock. Since +// column position is used to address the data, the stmt must start with +// `SELECT key_mr, data, timestamp`. This can be called repeatedly until +// stmt.Step() returns false, in which case the returned factom.EBlock will not +// be populated. +func SelectEBlock(stmt *sqlite.Stmt) (factom.EBlock, error) { + var eb factom.EBlock + hasRow, err := stmt.Step() + if err != nil { + return eb, err + } + if !hasRow { + return eb, nil + } + + eb.KeyMR = new(factom.Bytes32) + if stmt.ColumnBytes(0, eb.KeyMR[:]) != len(eb.KeyMR) { + return eb, fmt.Errorf("invalid key_mr length") + } + + // Load timestamp so that entries have correct timestamps. + eb.Timestamp = time.Unix(stmt.ColumnInt64(2), 0) + + data := make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, data) + if err := eb.UnmarshalBinary(data); err != nil { + return eb, fmt.Errorf("factom.EBlock{}.UnmarshalBinary(%x): %v", + data, err) + } + + return eb, nil +} + +func SelectEBlockByHeight(conn *sqlite.Conn, height uint32) (factom.EBlock, error) { + stmt := conn.Prep(SelectEBlockWhere + `db_height = ?;`) + stmt.BindInt64(1, int64(height)) + return SelectEBlock(stmt) +} + +func SelectEBlockBySequence(conn *sqlite.Conn, seq uint32) (factom.EBlock, error) { + stmt := conn.Prep(SelectEBlockWhere + `seq = ?;`) + stmt.BindInt64(1, int64(seq)) + return SelectEBlock(stmt) +} + +func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { + var keyMR factom.Bytes32 + stmt := conn.Prep(`SELECT key_mr FROM eblocks WHERE seq = ?;`) + stmt.BindInt64(1, int64(int32(seq))) // Properly convert uint32(0)-1 to -1 + hasRow, err := stmt.Step() + if err != nil { + return keyMR, err + } + if !hasRow { + return keyMR, nil + } + + if stmt.ColumnBytes(0, keyMR[:]) != len(keyMR) { + return keyMR, fmt.Errorf("invalid key_mr length") + } + + return keyMR, nil +} + +func SelectLatestEBlock(conn *sqlite.Conn) (factom.EBlock, factom.Bytes32, error) { + var dbKeyMR factom.Bytes32 + stmt := conn.Prep(`SELECT key_mr, data, timestamp, db_key_mr FROM eblocks + WHERE seq = (SELECT MAX(seq) FROM eblocks);`) + eb, err := SelectEBlock(stmt) + if err != nil { + return eb, dbKeyMR, err + } + if !eb.IsPopulated() { + return eb, dbKeyMR, nil + } + + if stmt.ColumnBytes(3, dbKeyMR[:]) != len(dbKeyMR) { + return eb, dbKeyMR, fmt.Errorf("invalid db_key_mr length") + } + + return eb, dbKeyMR, nil +} diff --git a/db/entry.go b/db/entry.go new file mode 100644 index 0000000..2b177b3 --- /dev/null +++ b/db/entry.go @@ -0,0 +1,90 @@ +package db + +import ( + "fmt" + "time" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/factom" +) + +// InsertEntry inserts e and returns the id if successful. +func InsertEntry(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) { + data, err := e.MarshalBinary() + if err != nil { + return -1, fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err) + } + + stmt := conn.Prep(`INSERT INTO entries + (eb_seq, timestamp, hash, data) + VALUES (?, ?, ?, ?);`) + stmt.BindInt64(1, int64(ebSeq)) + stmt.BindInt64(2, int64(e.Timestamp.Unix())) + stmt.BindBytes(3, e.Hash[:]) + stmt.BindBytes(4, data) + + if _, err := stmt.Step(); err != nil { + return 0, err + } + return conn.LastInsertRowID(), nil +} + +// MarkEntryValid sets valid to true for the entry with the given id. +func MarkEntryValid(conn *sqlite.Conn, id int64) error { + stmt := conn.Prep(`UPDATE entries SET valid = 1 WHERE id = ?;`) + stmt.BindInt64(1, id) + _, err := stmt.Step() + return err +} + +// SelectEntryWhere is a SQL fragment that must be appended with the condition +// of a WHERE clause and a final semi-colon. +const SelectEntryWhere = `SELECT hash, data, timestamp, valid FROM entries WHERE ` + +// SelectEntry uses stmt to populate and return a new factom.Entry and whether +// it is marked as valid. Since column position is used to address the data, +// the stmt must start with `SELECT hash, data, timestamp, valid`. This can be +// called repeatedly until stmt.Step() returns false, in which case the +// returned factom.Entry will not be populated. +func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, bool, error) { + var e factom.Entry + hasRow, err := stmt.Step() + if err != nil { + return e, false, err + } + if !hasRow { + return e, false, nil + } + + e.Hash = new(factom.Bytes32) + if stmt.ColumnBytes(0, e.Hash[:]) != len(e.Hash) { + return e, false, fmt.Errorf("invalid hash length") + } + + data := make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, data) + if err := e.UnmarshalBinary(data); err != nil { + return e, false, + fmt.Errorf("factom.Entry{}.UnmarshalBinary(%x): %v", + data, err) + } + + e.Timestamp = time.Unix(stmt.ColumnInt64(2), 0) + + return e, stmt.ColumnInt(3) > 0, nil +} + +// SelectEntryByID returns the factom.Entry with the given id. +func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, bool, error) { + stmt := conn.Prep(SelectEntryWhere + `id = ?;`) + stmt.BindInt64(1, id) + return SelectEntry(stmt) +} + +// SelectEntryByID returns the factom.Entry with the given hash. +func SelectEntryByHash(conn *sqlite.Conn, + hash *factom.Bytes32) (factom.Entry, bool, error) { + stmt := conn.Prep(SelectEntryWhere + `hash = ?;`) + stmt.BindBytes(1, hash[:]) + return SelectEntry(stmt) +} diff --git a/db/metadata.go b/db/metadata.go new file mode 100644 index 0000000..c8f8ab0 --- /dev/null +++ b/db/metadata.go @@ -0,0 +1,58 @@ +package db + +import ( + "fmt" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/factom" +) + +func InsertMetadata(conn *sqlite.Conn, + height uint32, dbKeyMR *factom.Bytes32, networkID [4]byte) error { + stmt := conn.Prep(`INSERT INTO metadata + (id, sync_height, sync_db_key_mr, network_id) + VALUES (?, ?, ?, ?);`) + stmt.BindInt64(1, 0) + stmt.BindInt64(2, int64(height)) + stmt.BindBytes(3, dbKeyMR[:]) + stmt.BindBytes(4, networkID[:]) + + _, err := stmt.Step() + return err + +} + +func SaveSync(conn *sqlite.Conn, height uint32, dbKeyMR *factom.Bytes32) error { + stmt := conn.Prep(`UPDATE metadata + (id, sync_height, sync_db_key_mr) + VALUES (?, ?, ?);`) + stmt.BindInt64(1, 0) + stmt.BindInt64(2, int64(height)) + stmt.BindBytes(3, dbKeyMR[:]) + _, err := stmt.Step() + return err +} + +func SelectMetadata(conn *sqlite.Conn) (uint32, factom.Bytes32, [4]byte, error) { + var dbKeyMR factom.Bytes32 + var networkID [4]byte + stmt := conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id + FROM metadata;`) + hasRow, err := stmt.Step() + if err != nil { + return 0, dbKeyMR, networkID, err + } + if !hasRow { + return 0, dbKeyMR, networkID, fmt.Errorf("no saved metadata") + } + + if stmt.ColumnBytes(1, dbKeyMR[:]) != len(dbKeyMR) { + return 0, dbKeyMR, networkID, fmt.Errorf("invalid sync_db_key_mr length") + } + + if stmt.ColumnBytes(2, networkID[:]) != len(networkID) { + return 0, dbKeyMR, networkID, fmt.Errorf("invalid network_id length") + } + + return uint32(stmt.ColumnInt64(0)), dbKeyMR, networkID, nil +} diff --git a/db/schema.go b/db/schema.go new file mode 100644 index 0000000..3232056 --- /dev/null +++ b/db/schema.go @@ -0,0 +1,136 @@ +package db + +import ( + "fmt" + + "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" +) + +const ( + createTableEBlocks = `CREATE TABLE eblocks ( + seq INTEGER PRIMARY KEY, + key_mr BLOB NOT NULL UNIQUE, + db_height INTEGER NOT NULL UNIQUE, + db_key_mr BLOB NOT NULL UNIQUE, + timestamp INTEGER NOT NULL, + data BLOB NOT NULL +); +` + + createTableEntries = `CREATE TABLE entries ( + id INTEGER PRIMARY KEY, + eb_seq INTEGER NOT NULL, + timestamp INTEGER NOT NULL, + valid BOOL NOT NULL DEFAULT FALSE, + hash BLOB NOT NULL UNIQUE, + data BLOB NOT NULL, + + FOREIGN KEY(eb_seq) REFERENCES eblocks +); +CREATE INDEX idx_entries_eb_seq ON entries(eb_seq); +` + + createTableAddresses = `CREATE TABLE addresses ( + id INTEGER PRIMARY KEY, + address BLOB NOT NULL UNIQUE, + balance INTEGER NOT NULL + CONSTRAINT "insufficient balance" CHECK (balance >= 0) +); +` + + createTableAddressTransactions = `CREATE TABLE address_transactions ( + entry_id INTEGER NOT NULL, + address_id INTEGER NOT NULL, + sent_to BOOL NOT NULL, + + PRIMARY KEY(entry_id, address_id), + + FOREIGN KEY(entry_id) REFERENCES entries, + FOREIGN KEY(address_id) REFERENCES addresses +); +CREATE INDEX idx_address_transactions_address_id ON address_transactions(address_id); +` + + createTableNFTokens = `CREATE TABLE nf_tokens ( + id INTEGER PRIMARY KEY, + metadata BLOB, + creation_entry_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL, + + FOREIGN KEY(creation_entry_id) REFERENCES entries, + FOREIGN KEY(owner_id) REFERENCES addresses +); +CREATE INDEX idx_nf_tokens_metadata ON nf_tokens(metadata); +CREATE INDEX idx_nf_tokens_owner_id ON nf_tokens(owner_id); +` + + createTableNFTokensTransactions = `CREATE TABLE nf_token_transactions ( + entry_id INTEGER NOT NULL, + nf_token_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL, + + PRIMARY KEY(entry_id, nf_token_id), + + FOREIGN KEY(entry_id) REFERENCES entries, + FOREIGN KEY(nf_token_id) REFERENCES nf_tokens, + FOREIGN KEY(owner_id) REFERENCES addresses +); +CREATE INDEX idx_nf_token_transactions_owner_id ON nf_token_transactions(owner_id); +CREATE INDEX idx_nf_token_transactions_nf_token_id ON nf_token_transactions(nf_token_id); +` + + createTableMetadata = `CREATE TABLE metadata ( + id INTEGER PRIMARY KEY, + sync_height INTEGER, + sync_db_key_mr BLOB, + network_id BLOB +);` + + // For the sake of simplicity, all chain DBs use the exact same schema, + // regardless of whether they actually make use of the NFTokens tables. + chainDBSchema = createTableEBlocks + + createTableEntries + + createTableAddresses + + createTableAddressTransactions + + createTableNFTokens + + createTableNFTokensTransactions + + createTableMetadata +) + +// validateOrApplySchema compares schema with the database connected to by +// conn. If the database has no schema, the schema is applied. Otherwise an +// error will be returned if the schema is not an exact match. +func validateOrApplySchema(conn *sqlite.Conn, schema string) error { + fullSchema, err := getFullSchema(conn) + if err != nil { + return err + } + if len(fullSchema) == 0 { + if err := sqlitex.ExecScript(conn, schema); err != nil { + return fmt.Errorf("failed to apply schema: %v", err) + } + return nil + } + if fullSchema != schema { + return fmt.Errorf("invalid schema: '%v'\n expected: '%#v'", + fullSchema, schema) + } + return nil +} +func getFullSchema(conn *sqlite.Conn) (string, error) { + const selectSchema = `SELECT sql FROM sqlite_master;` + var schema string + err := sqlitex.ExecTransient(conn, selectSchema, + func(stmt *sqlite.Stmt) error { + // Concatenate all non-empty table schemas. + if tableSchema := stmt.ColumnText(0); len(tableSchema) > 0 { + schema += tableSchema + ";\n" + } + return nil + }) + if err != nil { + return "", err + } + return schema, nil +} diff --git a/go.mod b/go.mod index 1745b39..c66119b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Factom-Asset-Tokens/fatd go 1.12 require ( + crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 diff --git a/go.sum b/go.sum index e9a79fd..c94b580 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,12 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.2 h1:4y4L7BdHenTfZL0HervofNTHh9Ad6mNX72cQvl+5eH0= cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= +crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw= +crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= +crawshaw.io/sqlite v0.1.2 h1:N17234KSbrpALUSEJ5AjTTf1u5Oym+FCXpBAs9Vi2Z4= +crawshaw.io/sqlite v0.1.2/go.mod h1:BZaitnE9BVpocOuCdi/y5XReJMUelG53e/rDSLwSFzY= +crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb h1:jKjr7R+PV9y9zuht2we+zbgGtsk8XiL1c8BhupMaYRQ= +crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea h1:8jg7gBfU9VSu5i12RTCgR4HzPt9ZZCLz+bz9/qFcRtw= @@ -33,6 +39,8 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc 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/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/crawshaw/sqlite v0.1.2 h1:q7e1kslCIqS4NymHpsflgO8yegxojYXIUje/SomDH50= +github.com/crawshaw/sqlite v0.1.2/go.mod h1:vpAFzhf2x15TYCbTzfbpQkAcnIFHnoVEiMN0/WF+RFo= 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= From 07506161368df6ca9de5bc077ac2578419a6689b Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 18 Jul 2019 16:57:56 -0800 Subject: [PATCH 004/124] feat(db): Add ValidateChain() to validate all EBlocks and Entries Correct UPDATE statements for Metadata. --- db/db_test.go | 35 ++++++----------- db/eblock.go | 4 +- db/entry.go | 2 +- db/metadata.go | 33 +++++++++++----- db/schema.go | 5 ++- db/validate.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 db/validate.go diff --git a/db/db_test.go b/db/db_test.go index 795b281..3783f5c 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -56,25 +56,7 @@ func TestDB(t *testing.T) { assert.EqualError(InsertEBlock(cp.Conn, eb, &dbKeyMR), "invalid EBlock{}.PrevKeyMR") - eID = 1 - for i, eb := range eblocks { - loadedEB, err := SelectEBlockBySequence(cp.Conn, uint32(i)) - require.NoError(err) - assert.Equal(loadedEB.KeyMR, eb.KeyMR) - for _, e := range eb.Entries { - loadByIDE, validI, err := SelectEntryByID(cp.Conn, eID) - eID++ - loadByHashE, validH, err := SelectEntryByHash( - cp.Conn, e.Hash) - assert.Equal(loadByIDE, loadByHashE) - assert.NoError(err) - assert.False(validI) - assert.False(validH) - assert.Equal(*loadByIDE.Hash, *e.Hash) - assert.Equal(loadByIDE.ExtIDs, e.ExtIDs) - assert.Equal(loadByIDE.Content, e.Content) - } - } + assert.NoError(ValidateChain(cp.Conn, eb.ChainID)) }) } @@ -83,7 +65,7 @@ var entryCount int func genNewEntry(chainID *factom.Bytes32) factom.Entry { extID := []byte(fmt.Sprintf("%v", entryCount)) entryCount++ - data := []byte(fmt.Sprintf("%v", time.Now())) + data := []byte("hello world") e := factom.Entry{ChainID: chainID, ExtIDs: []factom.Bytes{extID}, Content: data} hash, err := e.ComputeHash() if err != nil { @@ -97,7 +79,7 @@ func genChain() []factom.EBlock { eblocks := make([]factom.EBlock, 6) eb := &eblocks[0] height := uint32(10000) - timestamp := time.Now().Add(-7 * 24 * time.Hour).Round(time.Minute) + timestamp := time.Date(2019, 5, 5, 5, 0, 0, 0, time.Local) eb.Timestamp = timestamp eb.Height = 10000 eb.PrevKeyMR = new(factom.Bytes32) @@ -137,10 +119,17 @@ func genChain() []factom.EBlock { eb.PrevKeyMR = prevKeyMR eb.PrevFullHash = prevFullHash eb.Entries = make([]factom.Entry, 2) + lastTimestamp := eb.Timestamp + randMinRange := 10 for i := range eb.Entries { e := genNewEntry(chainID) - e.Timestamp = eb.Timestamp.Add( - time.Duration(rand.Intn(10)) * time.Minute) + // Ensure that the Timestamp is always greater than or + // equal to the last Entry timestamp. + rMin := rand.Intn(randMinRange) + randMinRange -= rMin + e.Timestamp = lastTimestamp.Add(time.Duration(rMin) * time.Minute) + lastTimestamp = e.Timestamp + eb.Entries[i] = e } bodyMR, err := eb.ComputeBodyMR() diff --git a/db/eblock.go b/db/eblock.go index 2d9a194..fe704b4 100644 --- a/db/eblock.go +++ b/db/eblock.go @@ -29,7 +29,7 @@ func InsertEBlock(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) stmt.BindBytes(2, eb.KeyMR[:]) stmt.BindInt64(3, int64(eb.Height)) stmt.BindBytes(4, dbKeyMR[:]) - stmt.BindInt64(5, int64(eb.Timestamp.Unix())) + stmt.BindInt64(5, eb.Timestamp.Unix()) stmt.BindBytes(6, data) _, err = stmt.Step() @@ -88,7 +88,7 @@ func SelectEBlockBySequence(conn *sqlite.Conn, seq uint32) (factom.EBlock, error func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { var keyMR factom.Bytes32 stmt := conn.Prep(`SELECT key_mr FROM eblocks WHERE seq = ?;`) - stmt.BindInt64(1, int64(int32(seq))) // Properly convert uint32(0)-1 to -1 + stmt.BindInt64(1, int64(int32(seq))) // Preserve uint32(-1) as -1 hasRow, err := stmt.Step() if err != nil { return keyMR, err diff --git a/db/entry.go b/db/entry.go index 2b177b3..ecc37f9 100644 --- a/db/entry.go +++ b/db/entry.go @@ -18,7 +18,7 @@ func InsertEntry(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) stmt := conn.Prep(`INSERT INTO entries (eb_seq, timestamp, hash, data) VALUES (?, ?, ?, ?);`) - stmt.BindInt64(1, int64(ebSeq)) + stmt.BindInt64(1, int64(int32(ebSeq))) // Preserve uint32(-1) as -1 stmt.BindInt64(2, int64(e.Timestamp.Unix())) stmt.BindBytes(3, e.Hash[:]) stmt.BindBytes(4, data) diff --git a/db/metadata.go b/db/metadata.go index c8f8ab0..8c0510a 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -23,9 +23,8 @@ func InsertMetadata(conn *sqlite.Conn, } func SaveSync(conn *sqlite.Conn, height uint32, dbKeyMR *factom.Bytes32) error { - stmt := conn.Prep(`UPDATE metadata - (id, sync_height, sync_db_key_mr) - VALUES (?, ?, ?);`) + stmt := conn.Prep(`UPDATE metadata SET + (id, sync_height, sync_db_key_mr) = (?, ?, ?);`) stmt.BindInt64(1, 0) stmt.BindInt64(2, int64(height)) stmt.BindBytes(3, dbKeyMR[:]) @@ -33,26 +32,40 @@ func SaveSync(conn *sqlite.Conn, height uint32, dbKeyMR *factom.Bytes32) error { return err } -func SelectMetadata(conn *sqlite.Conn) (uint32, factom.Bytes32, [4]byte, error) { +func SaveInitEntryID(conn *sqlite.Conn, entryID int64) error { + stmt := conn.Prep(`UPDATE metadata SET init_entry_id = ?;`) + stmt.BindInt64(1, entryID) + _, err := stmt.Step() + return err + +} + +func SelectMetadata(conn *sqlite.Conn) (int64, uint32, factom.Bytes32, [4]byte, error) { var dbKeyMR factom.Bytes32 var networkID [4]byte - stmt := conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id + stmt := conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id, init_entry_id FROM metadata;`) hasRow, err := stmt.Step() if err != nil { - return 0, dbKeyMR, networkID, err + return -1, 0, dbKeyMR, networkID, err } if !hasRow { - return 0, dbKeyMR, networkID, fmt.Errorf("no saved metadata") + return -1, 0, dbKeyMR, networkID, fmt.Errorf("no saved metadata") } if stmt.ColumnBytes(1, dbKeyMR[:]) != len(dbKeyMR) { - return 0, dbKeyMR, networkID, fmt.Errorf("invalid sync_db_key_mr length") + return -1, 0, dbKeyMR, networkID, + fmt.Errorf("invalid sync_db_key_mr length") } if stmt.ColumnBytes(2, networkID[:]) != len(networkID) { - return 0, dbKeyMR, networkID, fmt.Errorf("invalid network_id length") + return -1, 0, dbKeyMR, networkID, fmt.Errorf("invalid network_id length") + } + + var initEntryID int64 = -1 + if stmt.ColumnType(3) != sqlite.SQLITE_NULL { + initEntryID = stmt.ColumnInt64(3) } - return uint32(stmt.ColumnInt64(0)), dbKeyMR, networkID, nil + return initEntryID, uint32(stmt.ColumnInt64(0)), dbKeyMR, networkID, nil } diff --git a/db/schema.go b/db/schema.go index 3232056..93f0112 100644 --- a/db/schema.go +++ b/db/schema.go @@ -84,7 +84,10 @@ CREATE INDEX idx_nf_token_transactions_nf_token_id ON nf_token_transactions(nf_t id INTEGER PRIMARY KEY, sync_height INTEGER, sync_db_key_mr BLOB, - network_id BLOB + network_id BLOB, + init_entry_id INTEGER, + + FOREIGN KEY(init_entry_id) REFERENCES entries );` // For the sake of simplicity, all chain DBs use the exact same schema, diff --git a/db/validate.go b/db/validate.go new file mode 100644 index 0000000..406d4bb --- /dev/null +++ b/db/validate.go @@ -0,0 +1,101 @@ +package db + +import ( + "fmt" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/factom" +) + +// ValidateChain validates all Entry Hashes and EBlock KeyMRs, as well as the +// continuity of all stored EBlocks and Entries. It does not validate the +// validity of the saved DBlock KeyMRs. +func ValidateChain(conn *sqlite.Conn, chainID *factom.Bytes32) error { + eBlockStmt := conn.Prep(SelectEBlockWhere + `true;`) + entryStmt := conn.Prep(SelectEntryWhere + `true;`) + var prevKeyMR, prevFullHash factom.Bytes32 + var sequence uint32 + var eID int = 1 + for { + eb, err := SelectEBlock(eBlockStmt) + if err != nil { + return err + } + if !eb.IsPopulated() { + // No more EBlocks. + return nil + } + + if *eb.ChainID != *chainID { + return fmt.Errorf("invalid EBlock{%v, %v}: invalid ChainID", + eb.Sequence, eb.KeyMR) + } + + if sequence != eb.Sequence { + return fmt.Errorf("invalid EBlock{%v, %v}: invalid Sequence", + eb.Sequence, eb.KeyMR) + } + sequence++ + + if *eb.PrevKeyMR != prevKeyMR { + return fmt.Errorf("invalid EBlock{%v, %v}: broken PrevKeyMR link", + eb.Sequence, eb.KeyMR) + } + + if *eb.PrevFullHash != prevFullHash { + return fmt.Errorf("invalid EBlock{%v, %v}: broken FullHash link", + eb.Sequence, eb.KeyMR) + } + + keyMR, err := eb.ComputeKeyMR() + if err != nil { + return err + } + if keyMR != *eb.KeyMR { + return fmt.Errorf("invalid EBlock%+v: invalid KeyMR: %v", + eb, keyMR) + } + + prevFullHash, err = eb.ComputeFullHash() + if err != nil { + return err + } + prevKeyMR = keyMR + + for _, ebe := range eb.Entries { + e, valid, err := SelectEntry(entryStmt) + + if *e.Hash != *ebe.Hash { + return fmt.Errorf("invalid Entry{%v}: broken EBlock link", + e.Hash) + } + + hash, err := e.ComputeHash() + if err != nil { + return err + } + if hash != *e.Hash { + return fmt.Errorf("invalid Entry{%v}: invalid Hash", + e.Hash) + } + + if *e.ChainID != *chainID { + return fmt.Errorf("invalid Entry{%v}: invalid ChainID", + e.Hash) + } + + if e.Timestamp != ebe.Timestamp { + return fmt.Errorf( + "invalid Entry{%v, %v}: invalid Timestamp ebe %v e %v", + eID, e.Hash, ebe.Timestamp, e.Timestamp) + } + + // Attempt apply entry as fat entry. + if valid != false { + return fmt.Errorf("invalid Entry{%v}: marked as valid", + e.Hash) + } + eID++ + } + } +} From a0b528586d9d7fbe9a879955f4763fcf4324c8d1 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 31 Jul 2019 14:20:10 -0800 Subject: [PATCH 005/124] feat(factom): Add type NetworkID, Bytes32{}.IsZero(), other refactors The type NetworkID provides convenience features for working with the 4 magic bytes that identify different factom networks. MainnetID() and TestnetID() return the official mainnet and testnet Network IDs. Bytes32{}.IsZero() is a convenience function for checking if a Bytes32 object is zero. EBlock{}.GetAllPrev() was refactored to be easier to understand and correct. The behavior was also changed: the order of the returned []EBlock is now opposite, with the last element being the first EBlock in the chain. The Height field in Entry{} has been removed as it is not actually part of the binary datastructure and was only populated if the Entry{} was created by a call to EBlock{}.Get(). Now the Height needs to be passed separately. Consequently, Identity{} now has a Height field since it is no longer part of the embedded Entry{}. Identity{}.UnmarshalBinary unmarshals Entry data and verifies internal consistency before populating Identity{}. --- cli/cmd/root.go | 2 +- factom/bytes.go | 7 ++---- factom/bytes_test.go | 5 +++-- factom/dblock.go | 5 +---- factom/dblock_eblock_entry_test.go | 16 ++++++-------- factom/eblock.go | 19 ++++++++--------- factom/entry.go | 9 +++++--- factom/heights_test.go | 2 +- factom/identity.go | 23 ++++++++++++++++++-- factom/networkid.go | 34 ++++++++++++++++++++++++++++++ factom/pendingentries_test.go | 2 +- fat/chainid.go | 4 ++-- fat/entry_test.go | 6 ++---- fat/fat0/transaction_test.go | 2 +- fat/fat1/transaction_test.go | 2 +- fat/issuance_test.go | 2 +- flag/flag.go | 3 +-- srv/methods.go | 3 +-- srv/params.go | 2 +- state/chain.go | 4 ++-- state/db.go | 4 ++-- state/process.go | 15 ++++++------- 22 files changed, 106 insertions(+), 65 deletions(-) create mode 100644 factom/networkid.go diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 799f29f..21b504b 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -430,7 +430,7 @@ func validateChainIDFlags(cmd *cobra.Command, _ []string) error { return nil } func initChainID() { - NameIDs = fat.NameIDs(paramsToken.TokenID, *paramsToken.IssuerChainID) + NameIDs = fat.NameIDs(paramsToken.TokenID, paramsToken.IssuerChainID) *paramsToken.ChainID = factom.ChainID(NameIDs) vrbLog.Println("Token Chain ID:", paramsToken.ChainID) } diff --git a/factom/bytes.go b/factom/bytes.go index d11151e..a9aa144 100644 --- a/factom/bytes.go +++ b/factom/bytes.go @@ -156,9 +156,6 @@ func (b Bytes32) Value() (driver.Value, error) { var _ sql.Scanner = &Bytes32{} var _ driver.Valuer = Bytes32{} -var zeroBytes32 Bytes32 - -// ZeroBytes32 returns an all zero Byte32. -func ZeroBytes32() Bytes32 { - return zeroBytes32 +func (b Bytes32) IsZero() bool { + return b == Bytes32{} } diff --git a/factom/bytes_test.go b/factom/bytes_test.go index d68a091..be788d7 100644 --- a/factom/bytes_test.go +++ b/factom/bytes_test.go @@ -168,7 +168,8 @@ func TestBytes(t *testing.T) { assert.Equal(b[:], val) }) - t.Run("ZeroBytes32", func(t *testing.T) { - assert.Equal(t, Bytes32{}, ZeroBytes32()) + t.Run("IsZero()", func(t *testing.T) { + assert.True(t, Bytes32{}.IsZero()) + assert.False(t, Bytes32{0: 1}.IsZero()) }) } diff --git a/factom/dblock.go b/factom/dblock.go index e40cbd8..fbadcf1 100644 --- a/factom/dblock.go +++ b/factom/dblock.go @@ -36,9 +36,6 @@ import ( ) var ( - mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} - testnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA3} - adminBlockChainID = Bytes32{31: 0x0a} entryCreditBlockChainID = Bytes32{31: 0x0c} factoidBlockChainID = Bytes32{31: 0x0f} @@ -57,7 +54,7 @@ type DBlock struct { } type DBlockHeader struct { - NetworkID [4]byte `json:"networkid"` + NetworkID NetworkID `json:"networkid"` BodyMR *Bytes32 `json:"bodymr"` PrevKeyMR *Bytes32 `json:"prevkeymr"` diff --git a/factom/dblock_eblock_entry_test.go b/factom/dblock_eblock_entry_test.go index 51355e6..df648e3 100644 --- a/factom/dblock_eblock_entry_test.go +++ b/factom/dblock_eblock_entry_test.go @@ -36,7 +36,7 @@ var courtesyNode = "https://courtesy-node.factom.com" func TestDataStructures(t *testing.T) { height := uint32(166587) c := NewClient() - c.Factomd.DebugRequest = true + //c.Factomd.DebugRequest = true db := &DBlock{} db.Header.Height = height t.Run("DBlock", func(t *testing.T) { @@ -136,7 +136,6 @@ func TestDataStructures(t *testing.T) { assert.True(e.ChainID == eb.ChainID) assert.NotNil(e.Hash) assert.NotNil(e.Timestamp) - assert.Equal(height, e.Height) } assert.False(eb.IsFirst()) @@ -150,12 +149,12 @@ func TestDataStructures(t *testing.T) { ebs, err := eb.GetAllPrev(c) var first EBlock if assert.NoError(err) { - assert.Len(ebs, 6) - assert.True(ebs[0].IsFirst()) - first = ebs[0].Prev() - assert.Equal(first.KeyMR, ebs[0].KeyMR, + assert.Len(ebs, 5) + assert.True(ebs[len(ebs)-1].IsFirst()) + first = ebs[len(ebs)-1].Prev() + assert.Equal(first.KeyMR, ebs[len(ebs)-1].KeyMR, "Prev() should return a copy of itself if it is first") - assert.Equal(eb.KeyMR, ebs[len(ebs)-1].KeyMR) + assert.Equal(eb.KeyMR, ebs[0].KeyMR) } // Fetch the chain head EBlock via the ChainID. @@ -233,7 +232,6 @@ func TestDataStructures(t *testing.T) { // Validate the entry. assert.Len(e.ExtIDs, 6) assert.NotEmpty(e.Content) - assert.Equal(height, e.Height) assert.Equal(time.Unix(1542223080, 0), e.Timestamp) hash, err := e.ComputeHash() assert.NoError(err) @@ -245,6 +243,4 @@ func TestDataStructures(t *testing.T) { assert.NoError(err) assert.Equal(*e.Hash, hash) }) - - assert.Equal(t, Bytes32{}, ZeroBytes32()) } diff --git a/factom/eblock.go b/factom/eblock.go index 9d8e7f5..2ca3a0a 100644 --- a/factom/eblock.go +++ b/factom/eblock.go @@ -128,8 +128,7 @@ func (eb *EBlock) GetChainHead(c *Client) error { if err := c.FactomdRequest("chain-head", params, &result); err != nil { return err } - var zero Bytes32 - if *result.KeyMR == zero { + if result.KeyMR.IsZero() { if result.ChainInProcessList { return jrpc.Error{Message: "new chain in process list"} } else { @@ -144,7 +143,7 @@ func (eb *EBlock) GetChainHead(c *Client) error { // the PrevKeyMR being all zeroes. IsFirst returns false if eb is not populated // or if the PrevKeyMR is not all zeroes. func (eb EBlock) IsFirst() bool { - return eb.IsPopulated() && *eb.PrevKeyMR == zeroBytes32 + return eb.IsPopulated() && eb.PrevKeyMR.IsZero() } // Prev returns the an EBlock with its KeyMR initialized to eb.PrevKeyMR and @@ -158,19 +157,20 @@ func (eb EBlock) Prev() EBlock { } // GetAllPrev returns a slice of all preceding EBlocks in eb's chain, in order -// from earliest to latest, up to and including eb. So the last element of the -// returned slice is always equal to eb. If eb is the first entry block in its -// chain, then it is the only element in the slice. +// from eb to the first EBlock in the chain. So the 0th element of the returned +// slice is always equal to eb. If eb is the first entry block in its chain, +// then it is the only element in the slice. // // If you are only interested in obtaining the first entry block in eb's chain, // and not all of the intermediary ones, then use GetFirst to reduce network // calls and memory usage. func (eb EBlock) GetAllPrev(c *Client) ([]EBlock, error) { - ebs := []EBlock{eb} - for ; !ebs[0].IsFirst(); ebs = append([]EBlock{ebs[0].Prev()}, ebs...) { - if err := ebs[0].Get(c); err != nil { + ebs := []EBlock{} + for ; !eb.IsFirst(); eb = eb.Prev() { + if err := eb.Get(c); err != nil { return nil, err } + ebs = append(ebs, eb) } return ebs, nil } @@ -286,7 +286,6 @@ func (eb *EBlock) UnmarshalBinary(data []byte) error { e := &eb.Entries[ei] e.Timestamp = ts e.ChainID = eb.ChainID - e.Height = eb.Height obj := obj e.Hash = &obj ei-- diff --git a/factom/entry.go b/factom/entry.go index 894e1a7..ad37a5c 100644 --- a/factom/entry.go +++ b/factom/entry.go @@ -58,7 +58,6 @@ type Entry struct { Hash *Bytes32 `json:"entryhash,omitempty"` Timestamp time.Time `json:"-"` ChainID *Bytes32 `json:"chainid,omitempty"` - Height uint32 // Entry.Get populates the Content and ExtIDs. ExtIDs []Bytes `json:"extids"` @@ -365,8 +364,12 @@ func (e *Entry) MarshalBinary() ([]byte, error) { } copy(data[i:], e.Content) // Compute and save entry hash for later use - e.Hash = new(Bytes32) - *e.Hash = EntryHash(data) + if e.Hash == nil { + e.Hash = new(Bytes32) + } + if e.Hash.IsZero() { + *e.Hash = EntryHash(data) + } return data, nil } diff --git a/factom/heights_test.go b/factom/heights_test.go index 8a2e43b..a7f71e3 100644 --- a/factom/heights_test.go +++ b/factom/heights_test.go @@ -34,7 +34,7 @@ func TestHeights(t *testing.T) { c := NewClient() err := h.Get(c) assert.NoError(err) - zero := uint64(0) + zero := uint32(0) assert.NotEqual(zero, h.DirectoryBlock) assert.NotEqual(zero, h.Leader) assert.NotEqual(zero, h.EntryBlock) diff --git a/factom/identity.go b/factom/identity.go index 1eb26e5..46a58b3 100644 --- a/factom/identity.go +++ b/factom/identity.go @@ -60,7 +60,8 @@ func ValidIdentityNameIDs(nameIDs []Bytes) bool { // Identity represents the Token Issuer's Identity Chain and the public ID1Key. type Identity struct { - ID1 ID1Key + ID1 ID1Key + Height uint32 Entry } @@ -72,7 +73,7 @@ func NewIdentity(chainID *Bytes32) (i Identity) { // IsPopulated returns true if the Identity has been populated with an ID1Key. func (i Identity) IsPopulated() bool { - return i.ID1 != ID1Key(zeroBytes32) + return !Bytes32(i.ID1).IsZero() } // Get validates i.ChainID as an Identity Chain and parses out the ID1Key. @@ -103,8 +104,26 @@ func (i *Identity) Get(c *Client) error { return nil } + i.Height = eb.Height i.Entry = first copy(i.ID1[:], first.ExtIDs[2]) return nil } + +func (i *Identity) UnmarshalBinary(data []byte) error { + if err := i.Entry.UnmarshalBinary(data); err != nil { + return err + } + if !ValidIdentityChainID(i.ChainID[:]) { + return fmt.Errorf("invalid identity ChainID format") + } + if !ValidIdentityNameIDs(i.ExtIDs) { + return fmt.Errorf("invalid identity NameID format") + } + if *i.ChainID != ChainID(i.ExtIDs) { + return fmt.Errorf("invalid ExtIDs: Chain ID mismatch") + } + copy(i.ID1[:], i.ExtIDs[2]) + return nil +} diff --git a/factom/networkid.go b/factom/networkid.go new file mode 100644 index 0000000..66acfe8 --- /dev/null +++ b/factom/networkid.go @@ -0,0 +1,34 @@ +package factom + +var ( + mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} + testnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA3} +) + +func Mainnet() NetworkID { return mainnetID } +func Testnet() NetworkID { return testnetID } + +type NetworkID [4]byte + +func (n NetworkID) String() string { + switch n { + case mainnetID: + return "mainnet" + case testnetID: + return "testnet" + default: + return "custom: 0x" + Bytes(n[:]).String() + } +} + +func (n NetworkID) IsMainnet() bool { + return n == mainnetID +} + +func (n NetworkID) IsTestnet() bool { + return n == testnetID +} + +func (n NetworkID) IsCustom() bool { + return !n.IsMainnet() && !n.IsTestnet() +} diff --git a/factom/pendingentries_test.go b/factom/pendingentries_test.go index 16ca46d..3923cf2 100644 --- a/factom/pendingentries_test.go +++ b/factom/pendingentries_test.go @@ -43,7 +43,7 @@ var searchID = NewBytes32FromString( func TestPendingEntries(t *testing.T) { var pe PendingEntries c := NewClient() - c.Factomd.DebugRequest = true + //c.Factomd.DebugRequest = true assert := assert.New(t) require := require.New(t) require.NoError(pe.Get(c)) diff --git a/fat/chainid.go b/fat/chainid.go index e0dcdf3..bb44c69 100644 --- a/fat/chainid.go +++ b/fat/chainid.go @@ -41,7 +41,7 @@ func ValidTokenNameIDs(nameIDs []factom.Bytes) bool { } // NameIDs returns valid NameIDs -func NameIDs(tokenID string, issuerChainID factom.Bytes32) []factom.Bytes { +func NameIDs(tokenID string, issuerChainID *factom.Bytes32) []factom.Bytes { return []factom.Bytes{ []byte("token"), []byte(tokenID), []byte("issuer"), issuerChainID[:], @@ -49,6 +49,6 @@ func NameIDs(tokenID string, issuerChainID factom.Bytes32) []factom.Bytes { } // ChainID returns the chain ID for a given token ID and issuer Chain ID. -func ChainID(tokenID string, issuerChainID factom.Bytes32) factom.Bytes32 { +func ChainID(tokenID string, issuerChainID *factom.Bytes32) factom.Bytes32 { return factom.ChainID(NameIDs(tokenID, issuerChainID)) } diff --git a/fat/entry_test.go b/fat/entry_test.go index ae80d2b..d2e5ebb 100644 --- a/fat/entry_test.go +++ b/fat/entry_test.go @@ -81,8 +81,7 @@ var validExtIDsTests = []struct { Error: "timestamp salt expired", Entry: func() Entry { e := validEntry() - e.Timestamp = new(factom.Time) - *e.Timestamp = factom.Time(time.Now().Add(-48 * time.Hour)) + e.Timestamp = time.Now().Add(-48 * time.Hour) return e }(), }, { @@ -90,8 +89,7 @@ var validExtIDsTests = []struct { Error: "timestamp salt expired", Entry: func() Entry { e := validEntry() - e.Timestamp = new(factom.Time) - *e.Timestamp = factom.Time(time.Now().Add(48 * time.Hour)) + e.Timestamp = time.Now().Add(48 * time.Hour) return e }(), }, { diff --git a/fat/fat0/transaction_test.go b/fat/fat0/transaction_test.go index b389909..cff7c26 100644 --- a/fat/fat0/transaction_test.go +++ b/fat/fat0/transaction_test.go @@ -233,7 +233,7 @@ var ( coinbaseInputAmounts = []uint64{110} coinbaseOutputAmounts = []uint64{90, 20} - tokenChainID = fat.ChainID("test", *identityChainID) + tokenChainID = fat.ChainID("test", identityChainID) identityChainID = factom.NewBytes32(validIdentityChainID()) ) diff --git a/fat/fat1/transaction_test.go b/fat/fat1/transaction_test.go index 5e450d4..51739b3 100644 --- a/fat/fat1/transaction_test.go +++ b/fat/fat1/transaction_test.go @@ -252,7 +252,7 @@ var ( newNFTokens(NewNFTokenIDRange(6, 11))} identityChainID = factom.NewBytes32(validIdentityChainID()) - tokenChainID = fat.ChainID("test", *identityChainID) + tokenChainID = fat.ChainID("test", identityChainID) ) func newNFTokens(ids ...NFTokensSetter) NFTokens { diff --git a/fat/issuance_test.go b/fat/issuance_test.go index c8198fd..1ebdcbb 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -61,7 +61,7 @@ var ( func TestChainID(t *testing.T) { assert.Equal(t, "b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb", - ChainID("test", *identityChainID).String()) + ChainID("test", identityChainID).String()) } var validTokenNameIDsTests = []struct { diff --git a/flag/flag.go b/flag/flag.go index 81bcae5..94a460f 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -270,8 +270,7 @@ func Validate() { //log.Debugf("-walletcert %#v", FactomClient.Walletd.TLSCertFile) debugPrintln() - var zero factom.EsAddress - if EsAdr == zero { + if factom.Bytes32(EsAdr).IsZero() { EsAdr, _ = ECAdr.GetEsAddress(FactomClient) } else { ECAdr = EsAdr.ECAddress() diff --git a/srv/methods.go b/srv/methods.go index 82b3d3f..f1699af 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -416,8 +416,7 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { - var zero factom.EsAddress - if flag.EsAdr == zero { + if factom.Bytes32(flag.EsAdr).IsZero() { return ErrorNoEC } params := ParamsSendTransaction{} diff --git a/srv/params.go b/srv/params.go index ea54e7a..88e1791 100644 --- a/srv/params.go +++ b/srv/params.go @@ -72,7 +72,7 @@ func (p ParamsToken) ValidChainID() *factom.Bytes32 { if p.ChainID != nil { return p.ChainID } - chainID := fat.ChainID(p.TokenID, *p.IssuerChainID) + chainID := fat.ChainID(p.TokenID, p.IssuerChainID) p.ChainID = &chainID return p.ChainID } diff --git a/state/chain.go b/state/chain.go index 13a6544..4325adc 100644 --- a/state/chain.go +++ b/state/chain.go @@ -52,12 +52,12 @@ func (chain *Chain) ignore() { chain.ID = nil chain.ChainStatus = ChainStatusIgnored } -func (chain *Chain) track(first factom.Entry) error { +func (chain *Chain) track(height uint32, first factom.Entry) error { chain.ChainStatus = ChainStatusTracked chain.Identity.ChainID = factom.NewBytes32(first.ExtIDs[3]) chain.Metadata.Token = string(first.ExtIDs[1]) chain.Metadata.Issuer = chain.Identity.ChainID - chain.Metadata.Height = first.Height + chain.Metadata.Height = height if err := chain.setupDB(); err != nil { return err diff --git a/state/db.go b/state/db.go index 113b089..c2a30bc 100644 --- a/state/db.go +++ b/state/db.go @@ -268,8 +268,8 @@ func (chain *Chain) loadMetadata() error { if err := chain.First(&chain.Metadata).Error; err != nil { return err } - if !fat.ValidTokenNameIDs(fat.NameIDs(chain.Token, *chain.Issuer)) || - *chain.ID != fat.ChainID(chain.Token, *chain.Issuer) { + if !fat.ValidTokenNameIDs(fat.NameIDs(chain.Token, chain.Issuer)) || + *chain.ID != fat.ChainID(chain.Token, chain.Issuer) { return fmt.Errorf(`corrupted "metadata" table for chain %v`, chain.ID) } chain.Identity.ChainID = chain.Metadata.Issuer diff --git a/state/process.go b/state/process.go index 8124492..feb565d 100644 --- a/state/process.go +++ b/state/process.go @@ -69,7 +69,7 @@ func (chain *Chain) Process(eb factom.EBlock) error { } // Track this chain going forward. - if err := chain.track(first); err != nil { + if err := chain.track(eb.Height, first); err != nil { return err } if len(eb.Entries) == 1 { @@ -94,16 +94,15 @@ func (chain *Chain) process(eb factom.EBlock) (err error) { } chain.saveHeight(eb.Height) }() - es := eb.Entries if !chain.IsIssued() { - return chain.processIssuance(es) + return chain.processIssuance(eb) } - return chain.processTransactions(es) + return chain.processTransactions(eb.Entries) } // In general the following checks are ordered from cheapest to most expensive // in terms of computation and memory. -func (chain *Chain) processIssuance(es []factom.Entry) error { +func (chain *Chain) processIssuance(eb factom.EBlock) error { if !chain.Identity.IsPopulated() { // The Identity may not have existed when this chain was first tracked. // Attempt to retrieve it. @@ -116,11 +115,11 @@ func (chain *Chain) processIssuance(es []factom.Entry) error { } // If these entries were created in a lower block height than the // Identity entry, then none of them can be a valid Issuance entry. - if es[0].Height < chain.Identity.Height { + if eb.Height < chain.Identity.Height { return nil } - for i, e := range es { + for i, e := range eb.Entries { // If this entry was created before the Identity entry then it // can't be valid. if e.Timestamp.Before(chain.Identity.Timestamp) { @@ -143,7 +142,7 @@ func (chain *Chain) processIssuance(es []factom.Entry) error { } // Process remaining entries as transactions - return chain.processTransactions(es[i+1:]) + return chain.processTransactions(eb.Entries[i+1:]) } return nil } From a2f88f7f44e3a853e5ccf4f6a3f79ffc95d860b7 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 31 Jul 2019 14:28:53 -0800 Subject: [PATCH 006/124] feat(log): More flexible tags for loggers --- engine/engine.go | 2 +- log/log.go | 4 ++-- main.go | 2 +- srv/srv.go | 2 +- state/db.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 1c87da1..271079a 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -65,7 +65,7 @@ func engine(stop <-chan struct{}, done chan struct{}) { exit := func() { once.Do(func() { close(done) }) } defer exit() - log = _log.New("engine") + log = _log.New("pkg", "engine") if err := state.Load(); err != nil { log.Error(err) diff --git a/log/log.go b/log/log.go index 695e6fd..4c01c3a 100644 --- a/log/log.go +++ b/log/log.go @@ -32,7 +32,7 @@ type Log struct { *logrus.Entry } -func New(pkg string) Log { +func New(key string, value interface{}) Log { log := logrus.New() log.Formatter = &logrus.TextFormatter{ForceColors: true, DisableTimestamp: true, @@ -40,5 +40,5 @@ func New(pkg string) Log { if flag.LogDebug { log.SetLevel(logrus.DebugLevel) } - return Log{Entry: log.WithField("pkg", pkg)} + return Log{Entry: log.WithField(key, value)} } diff --git a/main.go b/main.go index b54d274..d93198d 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ func _main() (ret int) { sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) - log := log.New("main") + log := log.New("pkg", "main") log.Info("Fatd Version: ", flag.Revision) defer log.Info("Factom Asset Token Daemon stopped.") diff --git a/srv/srv.go b/srv/srv.go index 73a39a9..f063bec 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -45,7 +45,7 @@ var ( // server exits for any reason. If the done channel is closed before the stop // channel is closed, an error occurred. Errors are logged. func Start(stop <-chan struct{}) (done <-chan struct{}) { - log = _log.New("srv") + log = _log.New("pkg", "srv") // Set up JSON RPC 2.0 handler with correct headers. jrpc.DebugMethodFunc = true diff --git a/state/db.go b/state/db.go index c2a30bc..58dc6e1 100644 --- a/state/db.go +++ b/state/db.go @@ -50,7 +50,7 @@ var ( // Load state from all existing databases func Load() error { - log = _log.New("state") + log = _log.New("pkg", "state") // Try to create the database directory in case it doesn't already // exist. if err := os.Mkdir(flag.DBPath, 0755); err != nil && !os.IsExist(err) { From 9cd35fd175b5ffa4efe371a1887e2afa2c0574c5 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 31 Jul 2019 14:58:31 -0800 Subject: [PATCH 007/124] feat(fat): Add Validator{} and TokenIssuer() The interface Validator is implemented by fat.Issuance, fat0.Transaction, and fat1.Transaction. TokenIssuer() parses the token ID and Issuer Identity Chain ID from a given set of NameIDs. --- fat/chainid.go | 6 ++++++ fat/entry.go | 4 ++++ fat/fat0/transaction.go | 4 ++-- fat/fat0/transaction_test.go | 2 +- fat/fat1/transaction.go | 2 +- fat/fat1/transaction_test.go | 2 +- fat/issuance.go | 4 ++-- fat/issuance_test.go | 2 +- srv/methods.go | 4 ++-- state/process.go | 12 +++++++----- 10 files changed, 27 insertions(+), 15 deletions(-) diff --git a/fat/chainid.go b/fat/chainid.go index bb44c69..6d6d341 100644 --- a/fat/chainid.go +++ b/fat/chainid.go @@ -52,3 +52,9 @@ func NameIDs(tokenID string, issuerChainID *factom.Bytes32) []factom.Bytes { func ChainID(tokenID string, issuerChainID *factom.Bytes32) factom.Bytes32 { return factom.ChainID(NameIDs(tokenID, issuerChainID)) } + +func TokenIssuer(nameIDs []factom.Bytes) (string, *factom.Bytes32) { + var identityChainID factom.Bytes32 + copy(identityChainID[:], nameIDs[3]) + return string(nameIDs[1]), &identityChainID +} diff --git a/fat/entry.go b/fat/entry.go index 84fa4f3..03a0848 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -37,6 +37,10 @@ import ( "golang.org/x/crypto/ed25519" ) +type Validator interface { + Validate(factom.IDKey) error +} + // Entry has variables and methods common to all fat0 entries. type Entry struct { Metadata json.RawMessage `json:"metadata,omitempty"` diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index d767c40..5c3aa62 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -111,10 +111,10 @@ func (t Transaction) IsCoinbase() bool { return amount != 0 } -// Valid performs all validation checks and returns nil if t is a valid +// Validate performs all validation checks and returns nil if t is a valid // Transaction. If t is a coinbase transaction then idKey is used to validate // the RCD. Otherwise RCDs are checked against the input addresses. -func (t *Transaction) Valid(idKey factom.IDKey) error { +func (t *Transaction) Validate(idKey factom.IDKey) error { if err := t.UnmarshalEntry(); err != nil { return err } diff --git a/fat/fat0/transaction_test.go b/fat/fat0/transaction_test.go index cff7c26..ff6c610 100644 --- a/fat/fat0/transaction_test.go +++ b/fat/fat0/transaction_test.go @@ -207,7 +207,7 @@ func TestTransaction(t *testing.T) { assert := assert.New(t) tx := test.Tx key := test.IssuerKey - err := tx.Valid(&key) + err := tx.Validate(&key) if len(test.Error) != 0 { assert.Contains(err.Error(), test.Error) return diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index 74dad37..625d6df 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -149,7 +149,7 @@ func (t *Transaction) MarshalEntry() error { return t.Entry.MarshalEntry(t) } -func (t *Transaction) Valid(idKey factom.IDKey) error { +func (t *Transaction) Validate(idKey factom.IDKey) error { if err := t.UnmarshalEntry(); err != nil { return err } diff --git a/fat/fat1/transaction_test.go b/fat/fat1/transaction_test.go index 51739b3..1e74239 100644 --- a/fat/fat1/transaction_test.go +++ b/fat/fat1/transaction_test.go @@ -221,7 +221,7 @@ func TestTransaction(t *testing.T) { assert := assert.New(t) tx := test.Tx key := test.IssuerKey - err := tx.Valid(&key) + err := tx.Validate(&key) if len(test.Error) != 0 { assert.Contains(err.Error(), test.Error) return diff --git a/fat/issuance.go b/fat/issuance.go index ab2d7df..4a2eab9 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -102,9 +102,9 @@ func (i *Issuance) MarshalEntry() error { return i.Entry.MarshalEntry(i) } -// Valid performs all validation checks and returns nil if i is a valid +// Validate performs all validation checks and returns nil if i is a valid // Issuance. -func (i *Issuance) Valid(idKey factom.IDKey) error { +func (i *Issuance) Validate(idKey factom.IDKey) error { if err := i.UnmarshalEntry(); err != nil { return err } diff --git a/fat/issuance_test.go b/fat/issuance_test.go index 1ebdcbb..0ee086f 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -222,7 +222,7 @@ func TestIssuance(t *testing.T) { assert := assert.New(t) i := test.Issuance key := test.IssuerKey - err := i.Valid(&key) + err := i.Validate(&key) if len(test.Error) == 0 { assert.NoError(err) } else { diff --git a/srv/methods.go b/srv/methods.go index f1699af..ccf43d7 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -479,7 +479,7 @@ func sendTransaction(data json.RawMessage) interface{} { func validFAT0Transaction(chain *state.Chain, entry factom.Entry) error { tx := fat0.NewTransaction(entry) rpcErr := ErrorInvalidTransaction - if err := tx.Valid(chain.ID1); err != nil { + if err := tx.Validate(chain.ID1); err != nil { rpcErr.Data = err.Error() return rpcErr } @@ -509,7 +509,7 @@ func validFAT0Transaction(chain *state.Chain, entry factom.Entry) error { func validFAT1Transaction(chain *state.Chain, entry factom.Entry) error { tx := fat1.NewTransaction(entry) rpcErr := ErrorInvalidTransaction - if err := tx.Valid(chain.ID1); err != nil { + if err := tx.Validate(chain.ID1); err != nil { rpcErr.Data = err.Error() return rpcErr } diff --git a/state/process.go b/state/process.go index feb565d..bf7de5c 100644 --- a/state/process.go +++ b/state/process.go @@ -132,7 +132,7 @@ func (chain *Chain) processIssuance(eb factom.EBlock) error { return fmt.Errorf("Entry%+v.Get(c): %v", e, err) } issuance := fat.NewIssuance(e) - if err := issuance.Valid(&chain.Identity.ID1); err != nil { + if err := issuance.Validate(&chain.Identity.ID1); err != nil { log.Debugf("Invalid Issuance Entry: %v, %v", e.Hash, err) continue } @@ -155,8 +155,9 @@ func (chain *Chain) processTransactions(es []factom.Entry) error { switch chain.Type { case fat0.Type: transaction := fat0.NewTransaction(e) - if err := transaction.Valid(chain.Identity.ID1); err != nil { - log.Debugf("Invalid Transaction Entry: %v, %v", e.Hash, err) + if err := transaction.Validate(chain.Identity.ID1); err != nil { + log.Debugf("Invalid Transaction Entry: %v, %v", + e.Hash, err) continue } if err := chain.applyFAT0(transaction); err != nil { @@ -164,8 +165,9 @@ func (chain *Chain) processTransactions(es []factom.Entry) error { } case fat1.Type: transaction := fat1.NewTransaction(e) - if err := transaction.Valid(chain.Identity.ID1); err != nil { - log.Debugf("Invalid Transaction Entry: %v, %v", e.Hash, err) + if err := transaction.Validate(chain.Identity.ID1); err != nil { + log.Debugf("Invalid Transaction Entry: %v, %v", + e.Hash, err) continue } if err := chain.applyFAT1(transaction); err != nil { From 558645cb2d10fc533a626e677667ac8f14c01115 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 31 Jul 2019 15:05:03 -0800 Subject: [PATCH 008/124] feat(db): Add full database validation and tests A FAT0 and FAT1 database can be fully verified now. Any difference in state is reported in SQL and written out to a file. Go generate is used to create test databases, which are now checked into the repo. Running the validation test will reveal any differences in building the database as a result of future code changes. --- db/address.go | 69 ++- db/chain.go | 417 ++++++++++++++++++ db/connpool.go | 68 --- db/db.go | 26 +- db/db_test.go | 157 +------ db/eblock.go | 19 + db/entry.go | 59 ++- db/gen.go | 4 + db/gentestdb.go | 100 +++++ db/metadata.go | 123 ++++-- db/nftoken.go | 44 ++ db/schema.go | 49 +- ...f56169f94966341018b1950542f3dd.sqlite3-shm | Bin 0 -> 32768 bytes ...f56169f94966341018b1950542f3dd.sqlite3-wal | Bin 0 -> 943512 bytes ...ec561e7874a7b786ea3b66f2c6fdfb.sqlite3-shm | Bin 0 -> 32768 bytes ...ec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal | Bin 0 -> 1256632 bytes db/validate.go | 122 ++++- go.mod | 4 + go.sum | 11 + 19 files changed, 947 insertions(+), 325 deletions(-) create mode 100644 db/chain.go delete mode 100644 db/connpool.go create mode 100644 db/gen.go create mode 100644 db/gentestdb.go create mode 100644 db/nftoken.go create mode 100644 db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-shm create mode 100644 db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-wal create mode 100644 db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-shm create mode 100644 db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal diff --git a/db/address.go b/db/address.go index f28c4d4..fde42dd 100644 --- a/db/address.go +++ b/db/address.go @@ -9,50 +9,77 @@ import ( // AddressAdd adds add to the balance of adr, if it exists, otherwise it // inserts the address with balance set to add. -func AddressAdd(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) error { +func AddressAdd(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) (int64, error) { stmt := conn.Prep(`INSERT INTO addresses -(address, balance) VALUES (?, ?) -ON CONFLICT(address) DO -UPDATE SET balance = balance + ?;`) + (address, balance) VALUES (?, ?) + ON CONFLICT(address) DO + UPDATE SET balance = balance + excluded.balance;`) stmt.BindBytes(1, adr[:]) stmt.BindInt64(2, int64(add)) - stmt.BindInt64(3, int64(add)) _, err := stmt.Step() - return err + if err != nil { + return -1, err + } + return SelectAddressID(conn, adr) } // AddressSub subtracts sub from the balance of adr, only if this does not // cause balance to be < 0, otherwise an error is returned. -func AddressSub(conn *sqlite.Conn, adr *factom.FAAddress, sub uint64) error { +func AddressSub(conn *sqlite.Conn, adr *factom.FAAddress, sub uint64) (int64, error) { + id, err := SelectAddressID(conn, adr) + if err != nil { + return id, err + } + if id < 0 { + return id, fmt.Errorf("insufficient balance: %v", adr) + } stmt := conn.Prep(`UPDATE addresses -SET balance = balance - ? -WHERE address = ?;`) + SET balance = balance - ? + WHERE rowid = ?;`) stmt.BindInt64(1, int64(sub)) - stmt.BindBytes(2, adr[:]) + stmt.BindInt64(2, id) if _, err := stmt.Step(); err != nil { - return err + if sqlite.ErrCode(err) == sqlite.SQLITE_CONSTRAINT_CHECK { + return id, fmt.Errorf("insufficient balance: %v", adr) + } + return id, err } if conn.Changes() == 0 { - return fmt.Errorf("CHECK constraint failed: insufficient balance") + panic("no balances updated") } - return nil + return id, nil } -// SelectAddress returns the id and balance for the given adr. -func SelectAddress(conn *sqlite.Conn, adr *factom.FAAddress) (int64, uint64, error) { - stmt := conn.Prep(`SELECT id, balance FROM addresses WHERE address = ?;`) +func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, error) { + stmt := conn.Prep(`SELECT balance FROM addresses WHERE address = ?;`) stmt.BindBytes(1, adr[:]) - if _, err := stmt.Step(); err != nil { - return 0, 0, err + hasRow, err := stmt.Step() + if err != nil { + return 0, err + } + if !hasRow { + return 0, nil + } + return uint64(stmt.ColumnInt64(0)), nil +} +func SelectAddressID(conn *sqlite.Conn, adr *factom.FAAddress) (int64, error) { + stmt := conn.Prep(`SELECT rowid FROM addresses WHERE address = ?;`) + stmt.BindBytes(1, adr[:]) + hasRow, err := stmt.Step() + if err != nil { + return -1, err + } + if !hasRow { + return -1, nil } - return stmt.ColumnInt64(0), uint64(stmt.ColumnInt64(1)), nil + return stmt.ColumnInt64(0), nil } func InsertAddressTransaction(conn *sqlite.Conn, adrID int64, entryID int64, to bool) error { stmt := conn.Prep(`INSERT INTO address_transactions -(address_id, entry_id, sent_to) VALUES -(?, ?, ?)`) + (address_id, entry_id, sent_to) VALUES + (?, ?, ?)`) stmt.BindInt64(1, adrID) stmt.BindInt64(2, entryID) stmt.BindBool(3, to) diff --git a/db/chain.go b/db/chain.go new file mode 100644 index 0000000..8b74067 --- /dev/null +++ b/db/chain.go @@ -0,0 +1,417 @@ +package db + +import ( + "fmt" + "os" + "strings" + + "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" + + "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/fat/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/flag" + _log "github.com/Factom-Asset-Tokens/fatd/log" +) + +// Chain combines a READWRITE sqlite.Conn and a READ_ONLY sqlitex.Pool with the +// corresponding ChainID. Only a single thread may use the Read/Write Conn at a +// time. Multi-threaded readers must Pool.Get a read-only Conn from the and +// must Pool.Put the Conn back when finished. +type Chain struct { + ID *factom.Bytes32 + TokenID string + IssuerChainID *factom.Bytes32 + Head factom.EBlock + DBKeyMR *factom.Bytes32 + factom.Identity + NetworkID factom.NetworkID + + SyncHeight uint32 + SyncDBKeyMR *factom.Bytes32 + + fat.Issuance + NumIssued uint64 + + *sqlite.Conn // Read/Write + *sqlitex.Pool // Read Only Pool + Log _log.Log + + apply ApplyFunc +} + +type ApplyFunc func(factom.Entry) error + +// Open a new Chain for the given chainID within flag.DBPath. Validate or apply +// chainDBSchema. +func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.NetworkID, + identity factom.Identity) (*Chain, error) { + fname := eb.ChainID.String() + dbFileExtension + path := flag.DBPath + "/" + fname + // Ensure that the database file doesn't already exist. + _, err := os.Stat(path) + if err == nil { + return nil, fmt.Errorf("already exists: %v", path) + } + if !os.IsNotExist(err) { // Any other error is unexpected. + return nil, err + } + + chain, err := open(fname) + if err != nil { + return nil, err + } + + chain.Head = eb + chain.ID = eb.ChainID + nameIDs := eb.Entries[0].ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + return nil, fmt.Errorf("invalid token chain Name IDs") + } + chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) + chain.DBKeyMR = dbKeyMR + chain.Identity = identity + chain.SyncHeight = eb.Height + chain.SyncDBKeyMR = dbKeyMR + chain.NetworkID = networkID + + if err := chain.InsertMetadata(); err != nil { + return nil, err + } + coinbase := fat.Coinbase() + if _, err := AddressAdd(chain.Conn, &coinbase, 0); err != nil { + return nil, err + } + + chain.apply = chain.ApplyIssuance + if err := chain.Apply(eb, dbKeyMR); err != nil { + return nil, err + } + + return chain, nil +} + +// Open an existing chain database. +func Open(fname string) (*Chain, error) { + chain, err := open(fname) + if err != nil { + return nil, err + } + + // Load NameIDs, so load the first entry. + first, err := SelectEntryByID(chain.Conn, 1) + if err != nil { + return nil, err + } + if !first.IsPopulated() { + // A database must always have at least one EBlock. + return nil, fmt.Errorf("no first entry") + } + + nameIDs := first.ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + return nil, fmt.Errorf("invalid token chain Name IDs") + } + chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) + + // Load Chain Head + eb, dbKeyMR, err := SelectLatestEBlock(chain.Conn) + if err != nil { + return nil, err + } + if !eb.IsPopulated() { + // A database must always have at least one EBlock. + return nil, fmt.Errorf("no eblock in database") + } + chain.Head = eb + chain.DBKeyMR = &dbKeyMR + chain.ID = eb.ChainID + + if err := chain.LoadMetadata(); err != nil { + return nil, err + } + + return chain, nil +} + +// open a READWRITE Conn and a READ_ONLY Pool for flag.DBPath + "/" + fname. +// Validate or apply schema. +func open(fname string) (*Chain, error) { + const baseFlags = sqlite.SQLITE_OPEN_WAL | + sqlite.SQLITE_OPEN_URI | + sqlite.SQLITE_OPEN_NOMUTEX + path := flag.DBPath + "/" + fname + flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE + conn, err := sqlite.OpenConn(path, flags) + if err != nil { + return nil, fmt.Errorf("sqlite.OpenConn(%q, %x): %v", + path, flags, err) + } + if err := validateOrApplySchema(conn, chainDBSchema); err != nil { + return nil, err + } + flags = baseFlags | sqlite.SQLITE_OPEN_READONLY + pool, err := sqlitex.Open(path, flags, PoolSize) + if err != nil { + return nil, fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", + path, flags, PoolSize, err) + } + return &Chain{Conn: conn, Pool: pool, + Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension)), + }, nil +} + +// Close the Conn and Pool. Log any errors. +func (chain *Chain) Close() { + if err := chain.Conn.Close(); err != nil { + chain.Log.Errorf("chain.Conn.Close(): %v", err) + } + if err := chain.Pool.Close(); err != nil { + chain.Log.Errorf("chain.Pool.Close(): %v", err) + } +} + +// Apply save the EBlock and all Entries and updates the chain state according +// to the FAT-0 and FAT-1 protocols. +func (chain *Chain) Apply(eb factom.EBlock, dbKeyMR *factom.Bytes32) (err error) { + defer sqlitex.Save(chain.Conn)(&err) + if err := InsertEBlock(chain.Conn, eb, dbKeyMR); err != nil { + return err + } + chain.Head = eb + for _, e := range eb.Entries { + if err := chain.apply(e); err != nil { + return err + } + } + return nil +} + +func (chain *Chain) ApplyIssuance(e factom.Entry) error { + eid, err := InsertEntry(chain.Conn, e, chain.Head.Sequence) + if err != nil { + return err + } + issuance := fat.NewIssuance(e) + if err = issuance.Validate(chain.ID1); err != nil { + chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, err) + return nil + } + // check sig and is valid + if err := SaveInitEntryID(chain.Conn, eid); err != nil { + return err + } + chain.Issuance = issuance + chain.SetApplyFunc() + chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) + return nil +} + +func (chain *Chain) ApplyFAT0Tx(e factom.Entry) (tx fat0.Transaction, err error) { + tx = fat0.NewTransaction(e) + valid, eID, err := chain.ApplyTx(e, &tx) + if err != nil { + return + } + if !valid { + return + } + + // Do not return, but log, any errors past this point as they are + // related to being unable to apply a transaction. + defer func() { + if err != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, err) + err = nil + } else { + var cbStr string + if tx.IsCoinbase() { + cbStr = "Coinbase " + } + chain.Log.Debugf("Valid %v %vTransaction: %v %+v", + chain.Type, cbStr, tx.Hash, tx) + } + }() + // But first rollback on any error. + defer sqlitex.Save(chain.Conn)(&err) + + if err = MarkEntryValid(chain.Conn, eID); err != nil { + return + } + + for adr, amount := range tx.Outputs { + var aID int64 + aID, err = AddressAdd(chain.Conn, &adr, amount) + if err != nil { + return + } + if err = InsertAddressTransaction(chain.Conn, + aID, eID, true); err != nil { + return + } + } + + if tx.IsCoinbase() { + addIssued := tx.Inputs[fat.Coinbase()] + if chain.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Supply { + err = fmt.Errorf("coinbase exceeds max supply") + return + } + if err = InsertAddressTransaction(chain.Conn, + 1, eID, false); err != nil { + return + } + err = chain.IncrementNumIssued(addIssued) + return + } + + for adr, amount := range tx.Inputs { + var aID int64 + aID, err = AddressSub(chain.Conn, &adr, amount) + if err != nil { + return + } + if err = InsertAddressTransaction(chain.Conn, + aID, eID, false); err != nil { + return + } + } + return +} + +func (chain *Chain) ApplyTx(e factom.Entry, tx fat.Validator) (bool, int64, error) { + eID, err := InsertEntry(chain.Conn, e, chain.Head.Sequence) + if err != nil { + return false, eID, err + } + if err := tx.Validate(chain.ID1); err != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, err) + return false, eID, nil + } + valid, err := CheckEntryUniqueValid(chain.Conn, eID, e.Hash) + if err != nil { + return false, eID, err + } + if !valid { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, "replay") + } + return valid, eID, nil +} + +func (chain *Chain) SetApplyFunc() { + // Adapt to match ApplyFunc. + switch chain.Type { + case fat0.Type: + chain.apply = func(e factom.Entry) error { + _, err := chain.ApplyFAT0Tx(e) + return err + } + case fat1.Type: + chain.apply = func(e factom.Entry) error { + _, err := chain.ApplyFAT1Tx(e) + return err + } + default: + panic("invalid type") + } +} + +func (chain *Chain) ApplyFAT1Tx(e factom.Entry) (tx fat1.Transaction, err error) { + tx = fat1.NewTransaction(e) + valid, eID, err := chain.ApplyTx(e, &tx) + if err != nil { + return + } + if !valid { + return + } + + // Do not return, but log, any errors past this point as they are + // related to being unable to apply a transaction. + defer func() { + if err != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, err) + err = nil + } else { + var cbStr string + if tx.IsCoinbase() { + cbStr = "Coinbase " + } + chain.Log.Debugf("Valid %v %vTransaction: %v %+v", + chain.Type, cbStr, tx.Hash, tx) + } + }() + // But first rollback on any error. + defer sqlitex.Save(chain.Conn)(&err) + + if err = MarkEntryValid(chain.Conn, eID); err != nil { + return + } + + for adr, nfTkns := range tx.Outputs { + var aID int64 + aID, err = AddressAdd(chain.Conn, &adr, uint64(len(nfTkns))) + if err != nil { + return + } + if err = InsertAddressTransaction(chain.Conn, + aID, eID, true); err != nil { + return + } + for nfID := range nfTkns { + if err = SetNFTokenOwner(chain.Conn, nfID, aID, eID); err != nil { + return + } + if err = InsertNFTokenTransaction(chain.Conn, + nfID, eID, aID); err != nil { + return + } + } + } + + if tx.IsCoinbase() { + nfTkns := tx.Inputs[fat.Coinbase()] + addIssued := uint64(len(nfTkns)) + if chain.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Supply { + err = fmt.Errorf("coinbase exceeds max supply") + return + } + if err = InsertAddressTransaction(chain.Conn, + 1, eID, false); err != nil { + return + } + for nfID := range nfTkns { + metadata := tx.TokenMetadata[nfID] + if len(metadata) == 0 { + continue + } + if err = AttachNFTokenMetadata(chain.Conn, + nfID, metadata); err != nil { + return + } + } + err = chain.IncrementNumIssued(addIssued) + return + } + + for adr, nfTkns := range tx.Inputs { + var aID int64 + aID, err = AddressSub(chain.Conn, &adr, uint64(len(nfTkns))) + if err != nil { + return + } + if err = InsertAddressTransaction(chain.Conn, + aID, eID, false); err != nil { + return + } + } + return +} diff --git a/db/connpool.go b/db/connpool.go deleted file mode 100644 index 359b383..0000000 --- a/db/connpool.go +++ /dev/null @@ -1,68 +0,0 @@ -package db - -import ( - "fmt" - - "crawshaw.io/sqlite" - "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/flag" -) - -// ConnPool combines a READWRITE sqlite.Conn and a READ_ONLY sqlitex.Pool with -// the corresponding ChainID. Only a single thread may use the Read/Write Conn -// at a time. Multi-threaded readers must Pool.Get a read-only Conn from the -// and must Pool.Put the Conn back when finished. -type ConnPool struct { - ChainID *factom.Bytes32 - *sqlite.Conn // Read/Write - *sqlitex.Pool // Read Only Pool -} - -// Open a new ConnPool for the given chainID within flag.DBPath. Validate or -// apply chainDBSchema. -func Open(chainID *factom.Bytes32) (ConnPool, error) { - cp, err := open(chainID.String()+dbFileExtension, chainDBSchema) - if err != nil { - return cp, err - } - cp.ChainID = chainID - return cp, nil -} - -// open a READWRITE Conn and a READ_ONLY Pool for flag.DBPath + "/" + fname. -// Validate or apply schema. -func open(fname, schema string) (ConnPool, error) { - const baseFlags = sqlite.SQLITE_OPEN_WAL | - sqlite.SQLITE_OPEN_URI | - sqlite.SQLITE_OPEN_NOMUTEX - var cp ConnPool - var err error - path := flag.DBPath + "/" + fname - flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE - cp.Conn, err = sqlite.OpenConn(path, flags) - if err != nil { - return cp, fmt.Errorf("sqlite.OpenConn(%q, %x): %v", - path, flags, err) - } - if err := validateOrApplySchema(cp.Conn, schema); err != nil { - return cp, err - } - flags = baseFlags | sqlite.SQLITE_OPEN_READONLY - cp.Pool, err = sqlitex.Open(path, flags, PoolSize) - if err != nil { - return cp, fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", - path, flags, PoolSize, err) - } - return cp, nil -} - -// Close the Conn and Pool. Log any errors. -func (cp ConnPool) Close() { - if err := cp.Conn.Close(); err != nil { - log.Errorf("%v: cp.Conn.Close(): %v", cp.ChainID, err) - } - if err := cp.Pool.Close(); err != nil { - log.Errorf("%v: cp.Pool.Close(): %v", cp.ChainID, err) - } -} diff --git a/db/db.go b/db/db.go index 4c01681..ab9e32f 100644 --- a/db/db.go +++ b/db/db.go @@ -22,8 +22,8 @@ const ( PoolSize = 10 ) -func OpenAll() (cps []ConnPool, err error) { - log = _log.New("db") +func OpenAll() (chains []*Chain, err error) { + log = _log.New("pkg", "db") // Try to create the database directory in case it doesn't already // exist. if err := os.Mkdir(flag.DBPath, 0755); err != nil { @@ -35,10 +35,10 @@ func OpenAll() (cps []ConnPool, err error) { defer func() { if err != nil { - for _, cp := range cps { - cp.Close() + for _, chain := range chains { + chain.Close() } - cps = nil + chains = nil } }() @@ -46,9 +46,9 @@ func OpenAll() (cps []ConnPool, err error) { // file names. files, err := ioutil.ReadDir(flag.DBPath) if err != nil { - return cps, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) + return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) } - cps = make([]ConnPool, 0, len(files)) + chains = make([]*Chain, 0, len(files)) for _, f := range files { fname := f.Name() chainID, err := fnameToChainID(fname) @@ -57,14 +57,16 @@ func OpenAll() (cps []ConnPool, err error) { continue } log.Debugf("Loading chain: %v", chainID) - cp, err := open(fname, chainDBSchema) + chain, err := Open(fname) if err != nil { - return cps, err + return nil, err } - cp.ChainID = chainID - cps = append(cps, cp) + if *chainID != *chain.ID { + return nil, fmt.Errorf("chain id does not match filename") + } + chains = append(chains, chain) } - return cps, nil + return chains, nil } func fnameToChainID(fname string) (*factom.Bytes32, error) { invalidFNameErr := fmt.Errorf("invalid filename: %v", fname) diff --git a/db/db_test.go b/db/db_test.go index 3783f5c..93d0217 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -2,158 +2,23 @@ package db import ( "fmt" - "io/ioutil" - "math/rand" - "os" "testing" - "time" - "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/flag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestDB(t *testing.T) { - var err error - flag.DBPath, err = ioutil.TempDir(os.TempDir(), "fatd.db-test") - defer func() { - if err := os.RemoveAll(flag.DBPath); err != nil { - fmt.Println("failed to remove temp dir:", err) - } - }() - require.NoError(t, err) - t.Run("Open", func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - chains, err := OpenAll() - assert.Empty(chains) - require.NoError(err) - eblocks := genChain() - cp, err := Open(eblocks[0].ChainID) - require.NoError(err) - defer cp.Close() - var dbKeyMR factom.Bytes32 - eID := int64(1) - for _, eb := range eblocks { - require.NoError(InsertEBlock(cp.Conn, eb, &dbKeyMR)) - dbKeyMR[0]++ - for _, e := range eb.Entries { - id, err := InsertEntry(cp.Conn, e, eb.Sequence) - require.NoError(err) - assert.Equal(id, eID) - eID++ - } - } - // Ensure only EBlocks with sequential KeyMRs and Sequence - // numbers can be inserted. - eb := eblocks[5] - eb.Sequence = 100 - assert.EqualError(InsertEBlock(cp.Conn, eb, &dbKeyMR), - "invalid EBlock{}.PrevKeyMR") - eb = eblocks[5] - eb.PrevKeyMR = new(factom.Bytes32) - assert.EqualError(InsertEBlock(cp.Conn, eb, &dbKeyMR), - "invalid EBlock{}.PrevKeyMR") - - assert.NoError(ValidateChain(cp.Conn, eb.ChainID)) - }) -} - -var entryCount int - -func genNewEntry(chainID *factom.Bytes32) factom.Entry { - extID := []byte(fmt.Sprintf("%v", entryCount)) - entryCount++ - data := []byte("hello world") - e := factom.Entry{ChainID: chainID, ExtIDs: []factom.Bytes{extID}, Content: data} - hash, err := e.ComputeHash() - if err != nil { - panic(err) - } - e.Hash = &hash - return e -} - -func genChain() []factom.EBlock { - eblocks := make([]factom.EBlock, 6) - eb := &eblocks[0] - height := uint32(10000) - timestamp := time.Date(2019, 5, 5, 5, 0, 0, 0, time.Local) - eb.Timestamp = timestamp - eb.Height = 10000 - eb.PrevKeyMR = new(factom.Bytes32) - eb.PrevFullHash = new(factom.Bytes32) - eb.Entries = []factom.Entry{genNewEntry(nil)} - eb.Entries[0].Timestamp = timestamp.Add(time.Duration(rand.Intn(10)) * time.Minute) - chainID := eb.Entries[0].ChainID - eb.ChainID = chainID - - bodyMR, err := eb.ComputeBodyMR() - if err != nil { - panic(err) - } - eb.BodyMR = &bodyMR - - keyMR, err := eb.ComputeKeyMR() - if err != nil { - panic(err) - } - eb.KeyMR = &keyMR - - fullHash, err := eb.ComputeFullHash() - if err != nil { - panic(err) +func TestValidate(t *testing.T) { + require := require.New(t) + flag.DBPath = "./test-fatd.db" + flag.LogDebug = true + chains, err := OpenAll() + require.NoError(err, "OpenAll()") + require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) + + for _, chain := range chains { + fmt.Printf("%+v\n", chain) + assert.NoErrorf(t, chain.Validate(), "Chain{%v}.Validate()", chain.ID) } - prevKeyMR := &keyMR - prevFullHash := &fullHash - for i := range eblocks[1:] { - eb := &eblocks[i+1] - eb.Sequence = uint32(i + 1) - eb.ChainID = chainID - numBlocks := uint32(rand.Intn(10) + 1) - height += numBlocks - timestamp = timestamp.Add(time.Duration(numBlocks) * 10 * time.Minute) - eb.Timestamp = timestamp - eb.Height = height - eb.PrevKeyMR = prevKeyMR - eb.PrevFullHash = prevFullHash - eb.Entries = make([]factom.Entry, 2) - lastTimestamp := eb.Timestamp - randMinRange := 10 - for i := range eb.Entries { - e := genNewEntry(chainID) - // Ensure that the Timestamp is always greater than or - // equal to the last Entry timestamp. - rMin := rand.Intn(randMinRange) - randMinRange -= rMin - e.Timestamp = lastTimestamp.Add(time.Duration(rMin) * time.Minute) - lastTimestamp = e.Timestamp - - eb.Entries[i] = e - } - bodyMR, err := eb.ComputeBodyMR() - if err != nil { - panic(err) - } - eb.BodyMR = &bodyMR - - keyMR, err := eb.ComputeKeyMR() - if err != nil { - panic(err) - } - eb.KeyMR = &keyMR - prevKeyMR = &keyMR - - fullHash, err := eb.ComputeFullHash() - if err != nil { - panic(err) - } - prevFullHash = &fullHash - } - return eblocks -} - -func init() { - rand.Seed(100) } diff --git a/db/eblock.go b/db/eblock.go index fe704b4..6b9edbc 100644 --- a/db/eblock.go +++ b/db/eblock.go @@ -104,6 +104,25 @@ func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { return keyMR, nil } +func SelectDBKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { + var dbKeyMR factom.Bytes32 + stmt := conn.Prep(`SELECT db_key_mr FROM eblocks WHERE seq = ?;`) + stmt.BindInt64(1, int64(int32(seq))) // Preserve uint32(-1) as -1 + hasRow, err := stmt.Step() + if err != nil { + return dbKeyMR, err + } + if !hasRow { + return dbKeyMR, nil + } + + if stmt.ColumnBytes(0, dbKeyMR[:]) != len(dbKeyMR) { + return dbKeyMR, fmt.Errorf("invalid key_mr length") + } + + return dbKeyMR, nil +} + func SelectLatestEBlock(conn *sqlite.Conn) (factom.EBlock, factom.Bytes32, error) { var dbKeyMR factom.Bytes32 stmt := conn.Prep(`SELECT key_mr, data, timestamp, db_key_mr FROM eblocks diff --git a/db/entry.go b/db/entry.go index ecc37f9..6e38e48 100644 --- a/db/entry.go +++ b/db/entry.go @@ -39,52 +39,77 @@ func MarkEntryValid(conn *sqlite.Conn, id int64) error { // SelectEntryWhere is a SQL fragment that must be appended with the condition // of a WHERE clause and a final semi-colon. -const SelectEntryWhere = `SELECT hash, data, timestamp, valid FROM entries WHERE ` +const SelectEntryWhere = `SELECT hash, data, timestamp FROM entries WHERE ` // SelectEntry uses stmt to populate and return a new factom.Entry and whether // it is marked as valid. Since column position is used to address the data, -// the stmt must start with `SELECT hash, data, timestamp, valid`. This can be -// called repeatedly until stmt.Step() returns false, in which case the -// returned factom.Entry will not be populated. -func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, bool, error) { +// the stmt must start with `SELECT hash, data, timestamp`. This can be called +// repeatedly until stmt.Step() returns false, in which case the returned +// factom.Entry will not be populated. +func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { var e factom.Entry hasRow, err := stmt.Step() if err != nil { - return e, false, err + return e, err } if !hasRow { - return e, false, nil + return e, nil } e.Hash = new(factom.Bytes32) if stmt.ColumnBytes(0, e.Hash[:]) != len(e.Hash) { - return e, false, fmt.Errorf("invalid hash length") + return e, fmt.Errorf("invalid hash length") } data := make([]byte, stmt.ColumnLen(1)) stmt.ColumnBytes(1, data) if err := e.UnmarshalBinary(data); err != nil { - return e, false, - fmt.Errorf("factom.Entry{}.UnmarshalBinary(%x): %v", - data, err) + return e, fmt.Errorf("factom.Entry{}.UnmarshalBinary(%x): %v", + data, err) } e.Timestamp = time.Unix(stmt.ColumnInt64(2), 0) - return e, stmt.ColumnInt(3) > 0, nil + return e, nil } // SelectEntryByID returns the factom.Entry with the given id. -func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, bool, error) { +func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `id = ?;`) stmt.BindInt64(1, id) return SelectEntry(stmt) } -// SelectEntryByID returns the factom.Entry with the given hash. -func SelectEntryByHash(conn *sqlite.Conn, - hash *factom.Bytes32) (factom.Entry, bool, error) { +// SelectEntryByHash returns the factom.Entry with the given hash. +func SelectEntryByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `hash = ?;`) - stmt.BindBytes(1, hash[:]) return SelectEntry(stmt) } + +// SelectValidEntryByHash returns the factom.Entry with the given hash only if +// it is marked as valid. +func SelectValidEntryByHash(conn *sqlite.Conn, + hash *factom.Bytes32) (factom.Entry, error) { + stmt := conn.Prep(SelectEntryWhere + `hash = ? AND valid = true;`) + return SelectEntry(stmt) +} + +// CheckEntryUniqueValid checks if there have been any Entries earlier than id +// with the same hash that are valid. If so, then this Entry is a replay and +// should be ignored. Note that if the entry hash has already been saved but +// was invalid at the time, then the entry may be valid now. +func CheckEntryUniqueValid(conn *sqlite.Conn, + id int64, hash *factom.Bytes32) (bool, error) { + stmt := conn.Prep(`SELECT count(*) FROM entries WHERE + valid = true AND id < ? AND hash = ?;`) + stmt.BindInt64(1, id) + stmt.BindBytes(2, hash[:]) + hasRow, err := stmt.Step() + if err != nil { + return false, err + } + if !hasRow { + panic("should always return one row") + } + return stmt.ColumnInt(0) == 0, nil +} diff --git a/db/gen.go b/db/gen.go new file mode 100644 index 0000000..cd80c44 --- /dev/null +++ b/db/gen.go @@ -0,0 +1,4 @@ +package db + +//go:generate go run ./gentestdb.go -chainid b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb +//go:generate go run ./gentestdb.go -chainid 0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd diff --git a/db/gentestdb.go b/db/gentestdb.go new file mode 100644 index 0000000..da0f1e8 --- /dev/null +++ b/db/gentestdb.go @@ -0,0 +1,100 @@ +// +build ignore + +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/Factom-Asset-Tokens/fatd/db" + . "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + fflag "github.com/Factom-Asset-Tokens/fatd/flag" +) + +func init() { + log.SetFlags(log.Lshortfile) + fflag.DBPath = "./test-fatd.db" + fflag.LogDebug = true +} + +func main() { + if err := os.Mkdir(fflag.DBPath, 0755); err != nil { + if !os.IsExist(err) { + log.Fatalf("os.Mkdir(%#v): %v", fflag.DBPath, err) + } + } + c := NewClient() + chainID := NewBytes32FromString( + "b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb") + flag.Var(chainID, "chainid", "Chain ID to use for the test database") + flag.StringVar(&c.FactomdServer, "factomd", c.FactomdServer, "factomd endpoint") + flag.Parse() + + log.SetPrefix(fmt.Sprintf("ChainID: %v ", chainID.String())) + + eblocks, err := EBlock{ChainID: chainID}.GetAllPrev(c) + if err != nil { + log.Fatal(err) + } + + first := eblocks[len(eblocks)-1] + var dblock DBlock + dblock.Header.Height = first.Height + if err := dblock.Get(c); err != nil { + log.Fatal(err) + } + timestamp := dblock.Header.Timestamp + for i := range first.Entries { + e := &first.Entries[i] + if err := e.Get(c); err != nil { + log.Fatal(err) + } + e.Timestamp = timestamp.Add(e.Timestamp.Sub(first.Timestamp)) + } + first.Timestamp = timestamp + + nameIDs := first.Entries[0].ExtIDs + + if !fat.ValidTokenNameIDs(nameIDs) { + log.Fatalf("invalid token chain") + } + _, identityChainID := fat.TokenIssuer(nameIDs) + identity := NewIdentity(identityChainID) + if err := identity.Get(c); err != nil { + log.Fatal(err) + } + fmt.Println(identity) + + // We don't need the actual dbKeyMR + chain, err := db.OpenNew(first, dblock.KeyMR, Mainnet(), identity) + if err != nil { + log.Println(err) + return + } + defer chain.Close() + + eblocks = eblocks[:len(eblocks)-1] // skip first eblock + for i := range eblocks { + eb := eblocks[len(eblocks)-i-1] + var dblock DBlock + dblock.Header.Height = eb.Height + if err := dblock.Get(c); err != nil { + log.Fatal(err) + } + timestamp := dblock.Header.Timestamp + for i := range eb.Entries { + e := &eb.Entries[i] + if err := e.Get(c); err != nil { + log.Fatal(err) + } + e.Timestamp = timestamp.Add(e.Timestamp.Sub(eb.Timestamp)) + } + eb.Timestamp = timestamp + if err := chain.Apply(eb, dblock.KeyMR); err != nil { + log.Fatal(err) + } + } +} diff --git a/db/metadata.go b/db/metadata.go index 8c0510a..3290014 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -7,65 +7,118 @@ import ( "github.com/Factom-Asset-Tokens/fatd/factom" ) -func InsertMetadata(conn *sqlite.Conn, - height uint32, dbKeyMR *factom.Bytes32, networkID [4]byte) error { - stmt := conn.Prep(`INSERT INTO metadata - (id, sync_height, sync_db_key_mr, network_id) - VALUES (?, ?, ?, ?);`) - stmt.BindInt64(1, 0) - stmt.BindInt64(2, int64(height)) - stmt.BindBytes(3, dbKeyMR[:]) - stmt.BindBytes(4, networkID[:]) - +func (chain *Chain) InsertMetadata() error { + stmt := chain.Conn.Prep(`INSERT INTO metadata + (id, sync_height, sync_db_key_mr, network_id, id_key_entry, id_key_height) + VALUES (0, ?, ?, ?, ?, ?);`) + stmt.BindInt64(1, int64(chain.SyncHeight)) + stmt.BindBytes(2, chain.SyncDBKeyMR[:]) + stmt.BindBytes(3, chain.NetworkID[:]) + if chain.Identity.IsPopulated() { + data, err := chain.Identity.MarshalBinary() + if err != nil { + return err + } + fmt.Printf("bind bytes %x\n", data) + stmt.BindBytes(4, data) + stmt.BindInt64(5, int64(chain.Identity.Height)) + } else { + stmt.BindNull(4) + stmt.BindNull(5) + } _, err := stmt.Step() return err +} +func (chain *Chain) SaveSync() error { + stmt := chain.Conn.Prep(`UPDATE metadata SET + (sync_height, sync_db_key_mr) = (?, ?) WHERE id = 0;`) + stmt.BindInt64(1, int64(chain.SyncHeight)) + stmt.BindBytes(2, chain.SyncDBKeyMR[:]) + _, err := stmt.Step() + if chain.Conn.Changes() == 0 { + panic("nothing updated") + } + return err } -func SaveSync(conn *sqlite.Conn, height uint32, dbKeyMR *factom.Bytes32) error { +func SaveInitEntryID(conn *sqlite.Conn, id int64) error { stmt := conn.Prep(`UPDATE metadata SET - (id, sync_height, sync_db_key_mr) = (?, ?, ?);`) - stmt.BindInt64(1, 0) - stmt.BindInt64(2, int64(height)) - stmt.BindBytes(3, dbKeyMR[:]) + (init_entry_id, num_issued) = (?, 0) WHERE id = 0;`) + stmt.BindInt64(1, id) _, err := stmt.Step() + if conn.Changes() == 0 { + panic("nothing updated") + } return err } -func SaveInitEntryID(conn *sqlite.Conn, entryID int64) error { - stmt := conn.Prep(`UPDATE metadata SET init_entry_id = ?;`) - stmt.BindInt64(1, entryID) +func (chain *Chain) IncrementNumIssued(add uint64) error { + stmt := chain.Conn.Prep(`UPDATE metadata SET + num_issued = num_issued + ? WHERE id = 0;`) + stmt.BindInt64(1, int64(add)) _, err := stmt.Step() + if chain.Conn.Changes() == 0 { + panic("nothing updated") + } + chain.NumIssued += add return err - } -func SelectMetadata(conn *sqlite.Conn) (int64, uint32, factom.Bytes32, [4]byte, error) { - var dbKeyMR factom.Bytes32 - var networkID [4]byte - stmt := conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id, init_entry_id - FROM metadata;`) +func (chain *Chain) LoadMetadata() error { + stmt := chain.Conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id, + id_key_entry, id_key_height, init_entry_id, num_issued FROM metadata;`) hasRow, err := stmt.Step() if err != nil { - return -1, 0, dbKeyMR, networkID, err + return err } if !hasRow { - return -1, 0, dbKeyMR, networkID, fmt.Errorf("no saved metadata") + return fmt.Errorf("no saved metadata") } - if stmt.ColumnBytes(1, dbKeyMR[:]) != len(dbKeyMR) { - return -1, 0, dbKeyMR, networkID, - fmt.Errorf("invalid sync_db_key_mr length") + chain.SyncHeight = uint32(stmt.ColumnInt64(0)) + + chain.SyncDBKeyMR = new(factom.Bytes32) + if stmt.ColumnBytes(1, chain.SyncDBKeyMR[:]) != len(chain.SyncDBKeyMR) { + return fmt.Errorf("invalid sync_db_key_mr length") + } + + if stmt.ColumnBytes(2, chain.NetworkID[:]) != len(chain.NetworkID) { + return fmt.Errorf("invalid network_id length") } - if stmt.ColumnBytes(2, networkID[:]) != len(networkID) { - return -1, 0, dbKeyMR, networkID, fmt.Errorf("invalid network_id length") + // Load chain.Identity... + if stmt.ColumnType(3) == sqlite.SQLITE_NULL { + // No Identity, therefore no Issuance. + return nil + } + idKeyEntryData := make(factom.Bytes, stmt.ColumnLen(3)) + stmt.ColumnBytes(3, idKeyEntryData) + if err := chain.Identity.UnmarshalBinary(idKeyEntryData); err != nil { + return fmt.Errorf("chain.Identity.UnmarshalBinary(): %v", err) + } + chain.Identity.Height = uint32(stmt.ColumnInt64(4)) + if *chain.Identity.ChainID != *chain.IssuerChainID { + return fmt.Errorf("invalid chain.Identity.ChainID") } + chain.Identity.ChainID = chain.IssuerChainID // free mem from duplicates - var initEntryID int64 = -1 - if stmt.ColumnType(3) != sqlite.SQLITE_NULL { - initEntryID = stmt.ColumnInt64(3) + // Load chain.Issuance... + if stmt.ColumnType(5) == sqlite.SQLITE_NULL { + // No issuance entry so far... + return nil } + initEntryID := stmt.ColumnInt64(5) + chain.Issuance.Entry.Entry, err = SelectEntryByID(chain.Conn, initEntryID) + if err != nil { + return err + } + if err := chain.Issuance.Validate(chain.ID1); err != nil { + return err + } + chain.SetApplyFunc() + + chain.NumIssued = uint64(stmt.ColumnInt64(6)) - return initEntryID, uint32(stmt.ColumnInt64(0)), dbKeyMR, networkID, nil + return nil } diff --git a/db/nftoken.go b/db/nftoken.go new file mode 100644 index 0000000..00404b2 --- /dev/null +++ b/db/nftoken.go @@ -0,0 +1,44 @@ +package db + +import ( + "encoding/json" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" +) + +func SetNFTokenOwner(conn *sqlite.Conn, nfID fat1.NFTokenID, aID, eID int64) error { + stmt := conn.Prep(`INSERT INTO nf_tokens + (id, owner_id, creation_entry_id) VALUES (?, ?, ?) + ON CONFLICT(id) DO + UPDATE SET owner_id = excluded.owner_id;`) + stmt.BindInt64(1, int64(nfID)) + stmt.BindInt64(2, aID) + stmt.BindInt64(3, eID) + _, err := stmt.Step() + return err +} + +func AttachNFTokenMetadata(conn *sqlite.Conn, + nfID fat1.NFTokenID, metadata json.RawMessage) error { + stmt := conn.Prep(`UPDATE nf_tokens + SET metadata = ? WHERE id = ?;`) + stmt.BindBytes(1, metadata) + stmt.BindInt64(2, int64(nfID)) + _, err := stmt.Step() + if conn.Changes() == 0 { + panic("no NFTokenID updated") + } + return err +} + +func InsertNFTokenTransaction(conn *sqlite.Conn, + nfID fat1.NFTokenID, eID, aID int64) error { + stmt := conn.Prep(`INSERT INTO nf_token_transactions + (entry_id, nf_token_id, owner_id) VALUES (?, ?, ?);`) + stmt.BindInt64(1, eID) + stmt.BindInt64(2, int64(nfID)) + stmt.BindInt64(3, aID) + _, err := stmt.Step() + return err +} diff --git a/db/schema.go b/db/schema.go index 93f0112..7814271 100644 --- a/db/schema.go +++ b/db/schema.go @@ -23,16 +23,33 @@ const ( eb_seq INTEGER NOT NULL, timestamp INTEGER NOT NULL, valid BOOL NOT NULL DEFAULT FALSE, - hash BLOB NOT NULL UNIQUE, + hash BLOB NOT NULL, data BLOB NOT NULL, FOREIGN KEY(eb_seq) REFERENCES eblocks ); CREATE INDEX idx_entries_eb_seq ON entries(eb_seq); +CREATE INDEX idx_entries_hash ON entries(hash); ` - createTableAddresses = `CREATE TABLE addresses ( + createTableTempEntries = `CREATE TABLE temp.entries ( id INTEGER PRIMARY KEY, + timestamp INTEGER NOT NULL, + valid BOOL NOT NULL DEFAULT FALSE, + hash BLOB NOT NULL, + data BLOB NOT NULL, +); +CREATE INDEX temp.idx_entries_hash ON entries(hash); +` + + createTableAddresses = `CREATE TABLE addresses ( + address BLOB PRIMARY KEY, + balance INTEGER NOT NULL + CONSTRAINT "insufficient balance" CHECK (balance >= 0) +); +` + + createTableTempAddresses = `CREATE TABLE temp.addresses ( address BLOB NOT NULL UNIQUE, balance INTEGER NOT NULL CONSTRAINT "insufficient balance" CHECK (balance >= 0) @@ -50,6 +67,19 @@ CREATE INDEX idx_entries_eb_seq ON entries(eb_seq); FOREIGN KEY(address_id) REFERENCES addresses ); CREATE INDEX idx_address_transactions_address_id ON address_transactions(address_id); +` + + createTableTempAddressTransactions = `CREATE TABLE temp.address_transactions ( + entry_id INTEGER NOT NULL, + address_id INTEGER NOT NULL, + sent_to BOOL NOT NULL, + + PRIMARY KEY(entry_id, address_id), + + FOREIGN KEY(entry_id) REFERENCES entries, + FOREIGN KEY(address_id) REFERENCES addresses +); +CREATE INDEX temp.idx_address_transactions_address_id ON address_transactions(address_id); ` createTableNFTokens = `CREATE TABLE nf_tokens ( @@ -82,13 +112,18 @@ CREATE INDEX idx_nf_token_transactions_nf_token_id ON nf_token_transactions(nf_t createTableMetadata = `CREATE TABLE metadata ( id INTEGER PRIMARY KEY, - sync_height INTEGER, - sync_db_key_mr BLOB, - network_id BLOB, + sync_height INTEGER NOT NULL, + sync_db_key_mr BLOB NOT NULL, + network_id BLOB NOT NULL, + id_key_entry BLOB, + id_key_height INTEGER, + init_entry_id INTEGER, + num_issued INTEGER, FOREIGN KEY(init_entry_id) REFERENCES entries -);` +); +` // For the sake of simplicity, all chain DBs use the exact same schema, // regardless of whether they actually make use of the NFTokens tables. @@ -116,7 +151,7 @@ func validateOrApplySchema(conn *sqlite.Conn, schema string) error { return nil } if fullSchema != schema { - return fmt.Errorf("invalid schema: '%v'\n expected: '%#v'", + return fmt.Errorf("invalid schema: %v\n expected: %v", fullSchema, schema) } return nil diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-shm b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-shm new file mode 100644 index 0000000000000000000000000000000000000000..849be6922238ea1db6213fe40813655c4d14a584 GIT binary patch literal 32768 zcmeI5XK+@64CX+`P5dS?lHZK8bnt;XPq@h`+|) zndHWWoZcf3WhZnve7W=WEit)K?Qbs0O1{-BG04wz)PK(g{=8k_$$t&+z4v(j;iLp9 zNlK9j5=lyvC=yM|kQfq6;z(H%Ps)+XJuEJ@OdwzR|}?1JaN*B8^EC(v&nK%}EQ= zk~~44B&|paNhN6{on(+qQdCEQjthP_UM??-m%}?_VBWpVDmcgcE(5ayy##WHdS)2C zi_2Z`ti}C5+V-6@zCPN3HlPh?1KNN#pbcmP+JH8o4QK<}fHt5FXam}SHlPh?1KNN# zpbcmP+JH8o4QK<}fHt5FXam}SHlPh?1KNN#pbcmP+JH8o4QK<}fHt5FXam}SHlPh? z1KNN#pbcmP+JH8o4HVMAGdNSoTYYtR1F6_^_sjp~o8id9hui^i?LM6@jJpXwyHDQ1 z**=S(gA+LW=``dxn?1PT;oLk@o!diR!DKAJ=fR0ZKiw0*i2B)UmX0?u6Q5wOy%B^? zUV=M!lDIc#1g2miz7TRXvFU|hO=J#gGO*9V)FzQja@$lgH%5&_HWuMalMzCi-uO+( z$#$De?6*CF(D_SoH(3qtN_!Pku^3+oxtiGY!S5zA2Q{s6z`@iekt4WauqL+zzJ|B4 z1RG682x_rPvg5KXZeAIBPC;O4$~( zu?pWimDwz8X>OIR&CRf{<6SJnX0st8s^@S{#K(SAQE zL(tFMY7j1)i=9$-#&M_enum?&EpI!`<$}t=_*>A;W~(mv$>!q=p1mCJH{2!YXKpnF zx#nW0lwC0wYjDD;%w}QB^JdR)%%sAqm++6Uqpfz`Fah(h7AI{DAa?EwywmnuF*iR( zLopHau?~5DhB%?^j#EzL^dGV!Z*!i3mH5v8s1Q#WhDrDU>v1K-KK76Afz$Sf5J`}L WO1vFC1-nJu{FuCqtA6CZwf_RLBdR0- literal 0 HcmV?d00001 diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-wal b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-wal new file mode 100644 index 0000000000000000000000000000000000000000..f1ccf7ecdcff857c9d7ff084f8a21413e4e9bf50 GIT binary patch literal 943512 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

Yb`XS|Bj5vDZQB6QyG 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�(}UE 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

  • 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 diff --git a/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal b/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal deleted file mode 100644 index c2a58dec3d0cd0a7bb3eb04dd9e714037f715c3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1256632 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

    Ws?H{uxuF<21hiq9HYUn9Dz`fFR*T< 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 C9HsXF diff --git a/db/validate.go b/db/validate.go index c9dea7f..1e334cb 100644 --- a/db/validate.go +++ b/db/validate.go @@ -59,7 +59,7 @@ func (chain Chain) Validate() (err error) { `) chain.NumIssued = 0 chain.Issuance = fat.Issuance{} - chain.apply = chain.ApplyIssuance + chain.apply = chain.applyIssuance eBlockStmt := read.Prep(SelectEBlockWhere + `true;`) // SELECT all EBlocks. entryStmt := read.Prep(SelectEntryWhere + `true;`) // SELECT all Entries. From 1e7ec1ee257bccb4c4132910615f3bafba181200 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 1 Aug 2019 12:56:30 -0800 Subject: [PATCH 010/124] build(go.mod): Update github.com/spf13/cobra --- go.mod | 18 +++++--- go.sum | 138 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 130 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 77cd176..a893741 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Factom-Asset-Tokens/fatd go 1.12 require ( + cloud.google.com/go v0.43.0 // indirect crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 @@ -10,20 +11,23 @@ require ( github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/gocraft/dbr v0.0.0-20190131145710-48a049970bd2 - github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jinzhu/gorm v1.9.4 github.com/jinzhu/now v1.0.0 // indirect - github.com/kr/pretty v0.1.0 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/magiconair/properties v1.8.1 // indirect github.com/mitchellh/go-homedir v1.1.0 + github.com/pelletier/go-toml v1.4.0 // indirect github.com/posener/complete v1.2.1 github.com/rs/cors v1.6.0 - github.com/sirupsen/logrus v1.4.1 - github.com/spf13/cobra v0.0.3 + github.com/sirupsen/logrus v1.4.2 + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cobra v0.0.5 + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.3 - github.com/spf13/viper v1.3.2 - github.com/stretchr/objx v0.2.0 // indirect + github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 - golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect ) replace github.com/gocraft/dbr => github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea diff --git a/go.sum b/go.sum index 533d587..e4658a7 100644 --- a/go.sum +++ b/go.sum @@ -3,12 +3,11 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.2 h1:4y4L7BdHenTfZL0HervofNTHh9Ad6mNX72cQvl+5eH0= cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= -crawshaw.io/sqlite v0.1.2 h1:N17234KSbrpALUSEJ5AjTTf1u5Oym+FCXpBAs9Vi2Z4= -crawshaw.io/sqlite v0.1.2/go.mod h1:BZaitnE9BVpocOuCdi/y5XReJMUelG53e/rDSLwSFzY= -crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb h1:jKjr7R+PV9y9zuht2we+zbgGtsk8XiL1c8BhupMaYRQ= -crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea h1:8jg7gBfU9VSu5i12RTCgR4HzPt9ZZCLz+bz9/qFcRtw= @@ -21,20 +20,16 @@ github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c h1:n+ha40YmIj3rnMvg4KkNUmPRHC+U0axIZFe51LVJdaM= github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730214510-e4fb7efdb5d6 h1:hVlVweZFMmcyprlwc+JbVYjF4PUV6ih5MTNVlBgHR1g= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730214510-e4fb7efdb5d6/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730223213-413396883909 h1:DPtIFlplKA4W+PsLD0JYyTMf4erlgiQ14nZUn1WwysY= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730223213-413396883909/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730234737-ed3ae2ef8019 h1:ehFc1GJ874RQOAQRBtyOAf4G1PbV7Gcqp3KHyNoECTU= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730234737-ed3ae2ef8019/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 h1:2gBp1whWHlQvpYS9vdlmGKYBvs1qwzrxmov8KjUnBGE= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -43,19 +38,25 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 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/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/crawshaw/sqlite v0.1.2 h1:q7e1kslCIqS4NymHpsflgO8yegxojYXIUje/SomDH50= -github.com/crawshaw/sqlite v0.1.2/go.mod h1:vpAFzhf2x15TYCbTzfbpQkAcnIFHnoVEiMN0/WF+RFo= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +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/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3 h1:3mNLx0iFqaq/Ssxqkjte26072KMu96uz1VBlbiZhQU4= github.com/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3/go.mod h1:EcO5fNtMZHCMjAvj8LE6T+5bphSdR6LQ75n+m1TtsFI= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -68,6 +69,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= @@ -75,32 +77,47 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -115,11 +132,15 @@ github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns= github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -133,6 +154,8 @@ github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= @@ -143,6 +166,7 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -152,6 +176,8 @@ github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTm github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -161,32 +187,47 @@ github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DK github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= @@ -194,11 +235,20 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= @@ -208,34 +258,48 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= @@ -243,44 +307,76 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -288,7 +384,9 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -297,3 +395,5 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From a5458332ead461ddad8b9575bbf23ac39795cc59 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 4 Aug 2019 19:20:00 -0800 Subject: [PATCH 011/124] refactor(engine): Replace use of state with db package This completely removes the use of the state package from the engine package. All of the database operations for applying new entries are in the db package. All remaining state functionality, like processing eblocks and passing them to the appropriate chain database is handled entirely by the engine package. Whitelisting and blacklisting chains is now available. Setting the network ID is possible and is checked against the Factom blockchain. Also users can -ignorenewchains to avoid tracking new FAT chains. --- cli/cmd/predict.go | 1 - db/apply.go | 9 +- db/chain.go | 4 +- db/gentestdb.go | 20 +-- db/metadata.go | 11 +- ...bad3f56169f94966341018b1950542f3dd.sqlite3 | Bin 0 -> 266240 bytes ...b10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 | Bin 0 -> 106496 bytes db/validate.go | 2 +- engine/chain.go | 102 +++++++++++ engine/chainmap.go | 162 ++++++++++++++++++ engine/chainstatus.go | 58 +++++++ engine/chainstatus_test.go | 72 ++++++++ engine/engine.go | 137 ++++++++------- engine/process.go | 101 +++++++++++ factom/dblock_eblock_entry_test.go | 6 +- factom/eblock.go | 67 +++++++- factom/networkid.go | 27 +++ flag/flag.go | 47 +++++ flag/{addresslist.go => list.go} | 32 +++- flag/predict.go | 10 +- state/db.go | 2 +- state/process.go | 1 - 22 files changed, 771 insertions(+), 100 deletions(-) create mode 100644 db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 create mode 100644 db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 create mode 100644 engine/chain.go create mode 100644 engine/chainmap.go create mode 100644 engine/chainstatus.go create mode 100644 engine/chainstatus_test.go create mode 100644 engine/process.go rename flag/{addresslist.go => list.go} (70%) diff --git a/cli/cmd/predict.go b/cli/cmd/predict.go index d540518..a08d2c0 100644 --- a/cli/cmd/predict.go +++ b/cli/cmd/predict.go @@ -35,7 +35,6 @@ import ( var logErr = func(_ ...interface{}) {} -// parseAPIFlags parses func parseAPIFlags() error { args := strings.Fields(os.Getenv("COMP_LINE"))[1:] if err := apiFlags.Parse(args); err != nil { diff --git a/db/apply.go b/db/apply.go index 3fa14e9..a48b325 100644 --- a/db/apply.go +++ b/db/apply.go @@ -12,7 +12,7 @@ import ( type applyFunc func(int64, factom.Entry) error -func (chain *Chain) Apply(eb factom.EBlock, dbKeyMR *factom.Bytes32) (err error) { +func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) { // Ensure entire EBlock is applied atomically. defer sqlitex.Save(chain.Conn)(&err) @@ -37,6 +37,13 @@ func (chain *Chain) Apply(eb factom.EBlock, dbKeyMR *factom.Bytes32) (err error) } func (chain *Chain) applyIssuance(ei int64, e factom.Entry) error { + // The Identity must exist prior to issuance. + if !chain.Identity.IsPopulated() || + e.Timestamp.Before(chain.Identity.Timestamp) { + chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, + "created before identity") + return nil + } issuance := fat.NewIssuance(e) if err := issuance.Validate(chain.ID1); err != nil { chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, err) diff --git a/db/chain.go b/db/chain.go index f34279a..4766508 100644 --- a/db/chain.go +++ b/db/chain.go @@ -49,7 +49,7 @@ type Chain struct { apply applyFunc } -func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.NetworkID, +func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, identity factom.Identity) (*Chain, error) { fname := eb.ChainID.String() + dbFileExtension path := flag.DBPath + "/" + fname @@ -91,7 +91,7 @@ func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.Network } chain.apply = chain.applyIssuance - if err := chain.Apply(eb, dbKeyMR); err != nil { + if err := chain.Apply(dbKeyMR, eb); err != nil { return nil, err } diff --git a/db/gentestdb.go b/db/gentestdb.go index 948b6f2..9aafed8 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -35,7 +35,7 @@ func main() { log.SetPrefix(fmt.Sprintf("ChainID: %v ", chainID.String())) - eblocks, err := EBlock{ChainID: chainID}.GetAllPrev(c) + eblocks, err := EBlock{ChainID: chainID}.GetPrevAll(c) if err != nil { log.Fatal(err) } @@ -68,7 +68,7 @@ func main() { } // We don't need the actual dbKeyMR - chain, err := db.OpenNew(first, dblock.KeyMR, Mainnet(), identity) + chain, err := db.OpenNew(dblock.KeyMR, first, Mainnet(), identity) if err != nil { log.Println(err) return @@ -78,21 +78,17 @@ func main() { eblocks = eblocks[:len(eblocks)-1] // skip first eblock for i := range eblocks { eb := eblocks[len(eblocks)-i-1] + if err := eb.GetEntries(c); err != nil { + log.Fatal(err) + } var dblock DBlock dblock.Header.Height = eb.Height if err := dblock.Get(c); err != nil { log.Fatal(err) } - timestamp := dblock.Header.Timestamp - for i := range eb.Entries { - e := &eb.Entries[i] - if err := e.Get(c); err != nil { - log.Fatal(err) - } - e.Timestamp = timestamp.Add(e.Timestamp.Sub(eb.Timestamp)) - } - eb.Timestamp = timestamp - if err := chain.Apply(eb, dblock.KeyMR); err != nil { + eb.SetTimestamp(dblock.Header.Timestamp) + + if err := chain.Apply(dblock.KeyMR, eb); err != nil { log.Fatal(err) } } diff --git a/db/metadata.go b/db/metadata.go index 7123f6f..49168ac 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -31,15 +31,20 @@ func (chain *Chain) insertMetadata() error { return err } -func (chain *Chain) SetSyncHeight() error { +func (chain *Chain) SetSync(height uint32, dbKeyMR *factom.Bytes32) error { + if height <= chain.SyncHeight { + return nil + } stmt := chain.Conn.Prep(`UPDATE metadata SET (sync_height, sync_db_key_mr) = (?, ?) WHERE id = 0;`) - stmt.BindInt64(1, int64(chain.SyncHeight)) - stmt.BindBytes(2, chain.SyncDBKeyMR[:]) + stmt.BindInt64(1, int64(height)) + stmt.BindBytes(2, dbKeyMR[:]) _, err := stmt.Step() if chain.Conn.Changes() == 0 { panic("nothing updated") } + chain.SyncHeight = height + chain.SyncDBKeyMR = dbKeyMR return err } diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..5e29e7916d5e82d28417dcbe3916205cf448e871 GIT binary patch literal 266240 zcmeFa2Xqz3vMxM5IS)ApA)-J8k+Tg#u_*TgSA!b*|NXTG5;A0vGUv=ml7_}bbp9x^S7cm7 z*NC_X7c+)74Xf5XETDO{>h;3{Tx~!hHNg27-PL7?bs9Dgs}FHj9aj z8`!&F&v5rF{>Ci2D|8eY6W4FBePG+{-Lafn@4TF|3Rb=Ij*f|r3y0QtB+mk`IUpdW zf3NW9*x3G&jyF65uZx)zJ1wp+9sN6woxRW?0j(hM{520_CLD2 z)>k3-RD$0P8y%qc=I{HH&ovIR5Ax@!_ikCd-{`$d_``g4^|625%)Op}Jyt~5uKglo zW9^AvC#{&dLpj30B#Q0(5xsMSNA!>D4dr*U`~5i+9^xMM4@)6klqF@Zkem3(pC)8% zxO*mU%h)u3JMrEbzdzYx3%OkcMVWtp95+IG$Fcs+aa`j+9>>)N#dkBL%(-)uq?~`4 z$gy7E=3h?O*!L!>8yDRP<)S;W-kU}5M$kP0*Vx5BM$In$A|vb&^nP5lE#0pF9IRaj zy8XHM=N8^9e}4}C>41NjiB9BvcNOn9b&vdq7Vk|$Ulv`;+^h`omGP#IQ~QS*7Vave zySnWCd1?GF|qxlqN2M*(}4?cdJGH* ztsNFxFQAZngsN2nN(8AvA&QteEO#nyij4JqLtWcM5;GUgPNKa9<}BFo?h&!w9WMWA z0ljZzuNJ3V$ZizWN)$7PWG8+9)MaGn@Yu+{j;-{!oub+M-J*sDUO~#7Jv&Lt4}<4) zm zfEr;ntF@@#JfLQ^`pv@LO(EN%-dRxcHy@IP;P-&v1AY(uKjMM-?1IcR;2Q7= z`G4$w;oCE_t4~?Kt=+)!t3NK%G-=xpx9$(CmFe=Wug`D%HZ8khTaF@T-JGuj4sZN0 z-|b9q?#4bF{Ckz&yXUmN5&Qk9-m7~Dw6EK7p7_b`*ZVSO?0=%!+3&@^6}F#x9ufVz zPE_eMS=N6lRDX7P?D8oQxvI4}*fIZj_VYq%!#5_5E5bL-^IF{-G_FhKFAI);v!+>W z>q6yTPq;U??XTx&lzF{x>(6uUwH&p*$D-a78viup;gX0MIfvKgA}{>%&G}P5NWtQf zHcNNB4NRDMW#Z%M1L?pB3_VwhY^9>VMb!Gfo<5}Ct~{MvmH2Gq$s^r9Dmpm%!m!a{ zS(4*^PGu0e`0TvvozzZ?-LNU;(yaokTUUI2>&TlIW#&DcKeSDUxAVXJx_qeA`P9V5 zY09^++v3C2ykkl&UW}SKW!UT9YZH15Zn`e}rjG5NZWGpY{vmkNo5qdX4ft|I$#0ff zXRhseIxa;Qf z^G_DM(l_Vs1v`4vE9R+pHrd`$(#nxHHn15lH)(O|>5Jn4PVKO0Srs-dSOLr~af9zjo=e>2TaxCI8#8;-Qx(=6us=%+%94cT9g3)h+Uu zRJ}8lMeig=rgmEFhQg~xy(yV_@w}HqdcV!rWlLOg=Pe`UTt3| zZK~c$d#CrJ7K2k7o+%kHxNFp#3EzyAXB4ZFv0JUvgWC^?*qre@;nMQgJ2&=dzHZj> z(;wWqA6m6y32ynK#SaQbZVs;g#hsn|PKCC(8hu1cUtvtttg#2>=hth_{y5FWx(BYt zloo&3ZhM7#r!YmKR{qHoRu$=3GPvlLrd!Ux+R^jnz8~{6+E-`s&`#SPG}<#H74MM1 z>pgxg^zg{q$NJshOD~;Se@4`l-rZJ=Y~A>rq9^6L{-$59j+^S%Nog6HuyD%YbjtTf z(^a|{^kT}B%A4lSs#0#~;Z*vkE0?%)GquZRH?Yz9pI%5)E7#AL`%Dd7J*j)WWes14 zH$U5R$63B?pGt{6ZnUqxDNSl~bL*0F{aOU86~28u?pdKPnLNYB{2FeaUt92-@pmp3 zu>$wDF8TATTMPC!U68j_{}v_I6!}USRwG|n+Ouox$89N4A){}|%GmNdX4cEwc-QWy zA2<7=eMISr;+oCh-)Q~Ap7xjcN|)&s6Y71IB714mu+*OAIhA%I zTKO33k`-gsv8GttXud6%-vfRR_&wnFfZqdt5BNRc_rQNM4@ewGXiAuXxQRzx!66oA zMUGQwY6S(E<`8(l|L3E>|6iiN{?iLi|EIQ}!QTHb z3n`2hYmPTEXtUIOa;*54D*XTa9`Jj>?*YFD{!e%yK30|)!Yd7uvI_0%hBr@jGjD2p z(3rlA8d9co`BG&ngp{bj zGv>pLKkk0GsCdeRPfKMC4;?>bojQ5BZ)TIU$8$CqR%dLV5>2;%|MjG`H>S2dUvyi6 zkK_sK28)`!y9QuXN{7aLe{Xkg81C40p*E7>Hfb;Bk-y7n#G zqi(%HEe6$y8Q8e>kotWFMGt6GqhFf_arL|PYFeXnzd>z>#I-3CSh3yEz+TZYffYkS zf&+U+3<|7Rro`|L!-E5R_m6w8L`zIdOy|SfmwYmMM?@xZd8Mh%k4%ir6Ce9UOOb6zjuO!bhzn(7S(cJExb=Ag!1dNz*e9@{G>ZeY!j-ref;XkBYa`I^o8_Ac3_ zeyh0H?p<4yE?28b%eFN_=~OQpTrRkLaE0KI5*>p7zpld)!6CsVgG&XM4ldJS`0)7t z^t}lXoN}`5>!eFeb-(gI9k7yRN|!BPf-aM9rZl=%`CxaJvBSjSeHC)-u=!+f;RpT_~gZ~@16|qG`4z&iVeR1weF+f`GNf} z#f1K}w_TpP`?u#B(`3=Jy|>fu6!<46b7za1)8t=*Q^=6!r6X$g2^-KQrg7iKq0!x{ zmx>xttAFVxrF%7R-KJ#8=;;0}%T}n-H!P%2!|q*cmTX`<)(KAU2CT$i4cY&X1^6#L zQ@_(UGQ8*a)b^1x|2uv!XNRZM{r>Cr(HwK)tb8T%*XO%`{Jr-+ZES7xoYcw5wzuJP#u>Sg_J>Ua9Chj)J0Q{Dgn zzv1^XWy<^`zt7BjcTUW21ZAa`|w~ zi7Hv9b}PQA`}5)X2WI%S@DurZ_^tlSdh5)gfpLTTLl$QwnZ7#hYp=_Y>JI)?FS8OOz{|!NIVvJ9B#yp%k`dtr=lz&~ z(W6I8x1R0zV!?)>u5Gt`_arf}+N4)i2Mx#+x3_HJ!!`2TzyHhDDaLwZy|kWK53JkP zRqH%e`2YDm;P-&v1AY(qJ>d6%-vfRR_&wnFfZqdt5BNRc_rU+n9uPP}1lg9nWJ^x8 zB`bjBL2^8&5SH!$P~iFBJkOZFS-s5j)+bh8tG2b(y7YhZ1^Um;?*YFD{2uUo!0!RS z2mBuJd%*7jzX$vt@O!}T0nY<=(tc`Ll(KDN<`CfY`+IP;P-&v1AY(qJ>d6% z-vfRR_&xAH%mev&`cU6PA7zxN%yzmwV?vbjYKAhs!KVWr_B+|<^}`{HrrfQaDCTFm z25i)rwc{7|=#zcR2t_Oy*lk_w>X#;r$YiX)+_vkshdc6@*Va+r)t6K2jLFN7Jd=-Fk<_e&yoC0}d6%-vfRR_&wnFfZqdt5BNRspZ9>BB#_!y0Py>NYR~rY`+sWRIl%A#sXhPO z@BcHb&l&3#-Ti+L{{G)7>xi|_+F@<9R$2>bmH(gL1AY(qJ>d6%-vfRR_&wnFfZqdt z5BNRc_kiC6eh>WDc|bIHLS&g2b&872ike-PSW&gh5-%!tS!6}oE{mKf0rG;#Q*%KS z?4rO5yj|o)&Mxw-z}iJlBzBQw?ce_mYZhZYwO(5{t^3w#x)Wfhwck2wt+F;*i)fYq zpWg$15BNRc_kiC6eh>IP;P-&v1AY(qJ>d6%-vfRR{1Tdpu}p3yb36? z^s4}BCvq|(FWJxkM1ZdY2z~yCBF{rD&vH=YSPJi+|F?*YFD{2uUo!0!RS2mBuJd%*7jzX$$}2ke*l z^9H=iUk9myMg^|el5_q5fc>I>=l%aSegF0UJVz8(phsb?pY3DiwSKXFwpy#P@<{2R zvCJxH_=FN-2`#Hx%F4y<<@0OXOrM%fxM8%Fx(Wr=WGkcGP*lu8)_P@`x>}fR9^^Zz zX}RI@@0?`hmL4hlm21il=}YmYyhJ(2EfSX+FNN*eFzKd{Q_gAb)SBy(oSy$eFD}kD zgUq(lMs=Z?TUe(*GB?QQ_{q{-L6Iu+Dg5tBMdQ3Oz#wWlC7osoQ}mv^B(;^!TQ8In zdIRpB(b$ZUkI6NpPsK;dTWcx%R!A@Jal#nLMGMdM{OnbBkJx~HX1=sWnvcwz!uQ-;aikET6fhf$W3)xuOeF_5 zQ&y}gN@p&gGRK@MwBpvNGc3*e-s;bV3&+J!Zjw1xZNaw`8gNJW;i_cLF%F0~m8sJ2 zLa5N6J)_){pIar&n&NqB99K_xptq7MTF1o-)_z4+S{oU~6I=`ZfLc^Lqt#U!YDdKW z;sIlaCWr;Jeo_WLyH!!C!6nPH`0M&j^;c7qKan4BSFP>BNb81}gS{t@*3a{c`LtSD zeTOkeSYk}oCaKYqD)%&r{!;!z>&}0!*5hNfj`|eik+?(6tBlgeD98Bn;(Gp^)Qhjr zJ(Jg3J>~0SPxHJMtYRbh`!#?^a{d$ZI97QpRF&I zTNz{3iN<;@S&onx?L&2#v|Ozw72=C&f&4Lbi&02SC;80E{5~m{c8^PAMd&B^Xvr5q zr!Jl6QbJQCtdzpJEU~4_5}LRyslCe*+PEw}qszLuB&CDP5(>I3k#Sjk9gpR9ndy>5 zuahL+)8%kkLQR*&d*>AHs**~$EYZ0xR!SJ?>U{PxrPs+iDUN-j(2?lNCzmnB7bERV~AU6K;&vgFbp^BkL8!PO;(xU8*9QX*ZJoXus4 z)m-NDeB$$T$(h{Rq)skNuw3TL=dzSm9`lALxtyy@dUv&+lM_6j`MlZVtLwIi&*Czl zH$S}V4s`3{y*ZZH%+)29@t8LP2|Zk0@?e)GHg{Q)*GqgUSLf^KGOseJx2y7bp7o`1 zbt#I=5_7pMp_|Jpx+KvXfdtP_@m{T`N@?siNb&|RDW|LRyq*~6suI1Wkl>9(g12On zqg=b>8ZJx9?6QPtkM(s~ytf2>g#=-keE%H?p3~2DoM^ z-tOOEua(R~}6mVIJH}ey{H5f0u zwLWj+rF`h=in+{lSvFS_@Ad2RM%$O(t?lU&$t8)6T$V7%Wj=3?`@C`TW>~TpD5+tS(0~DpBD|1y^fO& z&n}nCe4hJLO1rwGelAP!0&K!Nop)BHT(cC_Wl5f6lPp&kU&&=D-n5Q?$I803KJTQG zz0jEKO~Zu7o}D*7DPD|eeQBC-3t*_Lh=21rt3_ zByw)c#4wj7c+1vT&ei!m$G*GP)^25TJ(neTi#5TUfe9Vl+W5LIOHAjoS}rNV7HajB22yQf zhuX`$XNg*R{e*c_xn>p>XId5cFT~8+S4tV0H1Vr4oWHCdl?0`@GEn0nMf1<) zarzH>7G;L1M2g}IU6DE(lL zl#^7&*esWl^GNYhb3VUXMgG9*Lf`MuLEObhSgn*-@@2K1vR;~_{b(`LA~nM3Zgke} z$_unG>nlAS-%f6?m$eFswUm|e8exQf$m(s~6*pL`<&nx$wVXW53e@Kdb*1BCPob2O zPTZy}mcJ5eh&iQed~PL3kCn5F6Rcw9YJQM9oo}I+&_3X^Dsz=D<)V6o_N|p@{-RD+ zf0biZ&N!uYwr;2))+OzduA5=%IH{*lR32<*)W;Y}MoC?i0*#Ss3pL4HYA9*}qprG1 zer%1EF6fM$QG6y(6Wa5Q#A@0sCDJTtaOOE}nlMQEO!!@REnb(t(T>PRq|??}WuNt< z8X#BDCYgDZYVtj)x>3Ni_yguN(L4VZEN9=QEy24TXMs6NOO+E8*%rMVHTMORc5a z0kx4B&QG(N7y-sweguC{jJN7ZPsK-kIYpHAYVE~~VkNbXu##VGekHb14;r_{JN!+t znX#N-suxzank}WuazRruKQQ(v4~@a*VB@XWKs+NoG&#PA_`4R%he$buJLXt@pBO2g zFy|XBrJdSfb(s;YZxa@4hW@F#TmHq!rnKR|(l3~e)oki>^Jk@*y4{#8H@4=R*@X#8 zPH`10a16^f1^J;Z$r)R6b3iTv*$(s(Ak%}!v?aS0WIfRT2J$Azt~|$(bWr&d$U3&< z8bf6j-~@OD;B|p_vK!?9#XykJpx$mvW*cY@+mgKp`V}C%foyF{=7=r1Sdh&@KM!O> zTQZ02?)%#Uu7J=lRq3@#wxW_Pve;z~Px^(;*#wY9ZOM$XO-h2IBFOx2_psD_ zqb->Uwqbi)z)5i-P^aJ1m4+}%xGJ3u-LdxHfN85gvr6e<*=|ESXf(RAJ~I0xi&T@I5%h7oEd8$ z7H3Xo=w>y@P%!+?mRtg8T7u?V;Fo}(2bt0Cpo>kp%b-|lOO^o+Q-I{QO)y4)Aq$to z<^Yp}AmQr6q2)S|0ib?qOC}yP&;eO%_f-TGWx-@T@Z+{*p4*bE1p2$6xnWBdrV2aX zZW;_G`#_ck*&bvCTe2ax3CtAkHmI|Kx|%H+xOQd_=y4)mvpJavwTnQ14&)(__HwSqZ^spe9D|QFi z^s%jG)`Og6OAaPDn+weE*oGBt%0_|$&X-iQ)i7e*NvOO4a<;9`2NYH}n;Y7|m6B05 z=b}K?0GsJhdk^FjkR5Evz?5WQvMm{$47-6tIO%J*gpnt(s2G?F z%oJPQ(-x9#%Qk{OUO~GBAg9}vicLA3=+R&z*y`G#UI=oVEm>$uYTA{VL2(7-0g&5m z^-!B~RY9`0#5*GWJKvNuaK>D9o3qD2wy_(*rLizcn2XQ|mIZkPjUIr6xWmG9WG6#Y zI23sc_As@W?`_WEdKm;Z5VKg;K1gFQ46njWB^V*X11^&!#l*y*tmo^Opx*Y1#*ph+J&;ryr8_@f(j0mg( zQrxa3h3)nkpe6W_g?k!v$yURpdSPIeaS*;Z1N3uj$$%%hO`v&d zOY$>FN zV8W59V7C?I4BHUA&%sQmKhWCU!h|8Dve zFK9it?yKX3tx{fLvOd%rr{`6_5!a}-h4p5*6>Zj#hbl$&Y36R>N2`l8Oxj}z<_qn% z{L&0kdAYw{!s?(dQPP<|iHdbdn=a&ahR1*H2zIqj-cU&|x@LMQf0lTo{9E3_}Q zMQU|rn|hNzm28!|DWin3VtpY&Tq+pmH2I=_QDoKja*UcNh*oZKp8gd-#;B|gmkS9; zjMmCv^9%E$T1l@#GX+i?m-*kVEPRwE$hulo?xfrmhRK55MytzhH+@>1@j!d671Jlm zyN!5hmGV|?VZGKujb74pF_Rf+-cx^5=c)zxcs^WNPoGBj(x=F7{8{xSKUB)WH{mt4 zp*CBO<4;&S^j^vf`3oV&D5GdbfSKLQz?V_?2-l>HRwF4)57&ChJ|oH~rt~u=iD5j? zy|td|t+^L^M!ul%tGQCCEmk##N@J`iO^V?*>Q$_k)-55sHJ5(Vb5E%#wK3o5 zrr4eThUR4i$`hq{zLz*$`c3;@+{-_)R5Mmtp^Z^{%cJF$;yG=fmd)yI9#a;oxuqca ztr4ewW?fcFS!cC^TDb9AnI-%zl`!^bPmQelHm$WV$4IAiP(C$3(nl)=^wY{wX}dK* z96-Op*&%e3n)1`tOL7h4tnM=xsb{!VVh~@K4;7k<5n6g_lGxiiA#boAD7nSk>T%oZMPTJqbiQu;!pE`QSs7C#X> zNoT~i>SUoFKS^oC-8Dz^8tji0);YkwbTz8^LWi;80 zkP|@zU}gbucuMRxD6#b2CieADMsQA|Czk3UtOQ_#07x)GR`7s`7A+9+0I)UCVgUfN z1>rMgaup%I+YugVdm!8-Z50umfzvm4FgCjZhn512iiH1$Gqbh5|63 zKvhH3okHCU&@}-mfQtPZ4IU!=gpi2P8GsoLz->k71i&6ez=_A=C9^9~Sp|UX1K@BF zxHhQV2*AulpdXj03!JH$8FcJwgirwT9RQbr%9g177E70~bRL1;3u#|npuk;5-BJLS z!4gg_k{b+7tU`Mhub9n&h6fSQQQ>H~4j}-mUIH-jsKma=TIje4>dKo=z02=~^I1#zqSd|T{ssS+QCT0)TIrIJ+l*mLh zS%h`x5Dp=f17LRm5WH}*5=sn?2ZOOT7RXT!!RxD3K6U-h#e9K*RD_wFFBzCD_iWlmQrL zmhHw8hQhwk2hJygtDM0}#!P{#o**S?!#2YHUSSUl5T=8L0tL=lXVGXVKviw5T8OX> zfWa>@kEk&>9`U;uK^qVryKT zE$#=d-`Zh3-s}g%8#&7 zNhpi}8G|ibqw#u#w5X)t3D8CzL6!#<7lbwA02pVMIBPx&*5w5teX;ZmT4#a+_azj< zu&^8r(f}|R&RSrVGtr#!!(~lyDU#w~LJC7y8L$uZJv$4#x&&3Y47mN+V*@nANyY8N z686a8eCBXCIRoqF05CWN+$K~$1t32oIHkpKI1DA+STvl5fN_shf)eKpd^>Di7J$Ly zaL!u90VF4}4n4%-Es?ZB<&OX?1_|N}#5GWIeE>*DEEPk@g@6~!ITwo|gLOWoB%#T5 z0QM@vE&v8Eonx?c9)L?h6Z}xo19c}5h69jl2vq@?SKuQo5R6RRc(kC8*>sZSfdaP- zfPI4S2&!^o)q2#;M5qZswgE8b066DF0#S+1XMckdi}QdC!@6|{E8zebaJaK*7!E+t z&CF^n%|?6rs7%kb0Z1G_=fvs0c>7Zvi&L9SMf0r)^vep`3Wtxwg~ru`DuVNo;P8>l zsKg-6rh{g+K;mo4lCILb>s~~Q3O0zBPg(^ z5e6Y(%&;HybD`ocr8p*J2u%oJ6zedR%m-SMb=VqXjkNk(J*^0< zwbjt7VO6wBSOu+IRz{k;PRy5fw!3-WJYgO*cbXf_WhTvSH^-VjbC4Nhb~W3Yjm?^7 zWwR8`?$2#zG&PeLFO5gWP2)Vxo~QZg#(HC!F~^u{j5U15K%MC`CI$a&F`qY8yM`~xam0Dk|rj}KUr~zt5RaF_~h4O&zZ#bhIR(2~} zl$FYSWx6t6iB|?HA1M(^OQoLjp;AUEtmIZQD2n`>{7k+pUzWd4J1Z+An<}t&tW9%8uIT&yEj6-$YQ z#GGO}Q4-z=PlO-n@7X7X1HyJ;ov>J#DNGcSgdsw2p{vkVXe87SDhS1dyh0Yi6gd7B z|A@cApW~16d-yHGVsEhL*rV(ob}PGzoo`P^ zIld-OSR%*QvVkK9Sx3W>Cae@6X20};3#ZB&P|vQTG!$t7rPZ<3OG>K&W#TFQ5DiR9 zKR|=Ew6ChrR}reLg7sx7t&H{KDXoOspE0#L3JrRCA~E~Vwr;0C2-fwI>r zErWDEJy>ZtSTI$V0wuSP(vnC^QyPM_J*6d(R-m*vP&R~iPz*X~OO-{j{x+q-NV8E| z1Wl_^S{NwvEu}$7_fT30Z8K3?5N)qfS^y}SNNIkwT|{X7D%hJpk+%+GXrIt zQJM*98A>xE)hW$@w)ZJb50o2BX*x7$PH9@CXDLmCwxuYwfHGI;aZNa`eK7j=RXXk1 z$CT0+uu{sbrRE0+n%; z4o8}s(qTXe9Wmcfq@ySu0+frQbTHBylnw&QPN#Gr+TNpd0MaLv_D9-*(m0^Z3QA*< zCQ{lDC`ZS^*B5DiO8Wq1XHeQ3=?F?=kanW97t*?vegu@Hp_kSZE^Po+_5dYEXO%A+ zOd8_M)^aQ06G5U4W9$Dea6jgwhB!xJ8fK36A>#RfdC- zD^F=hG+jbz2h`GK|E63zVTN+1CbbcT?ILwQJ}RTEP+eQq~d}U8lYl z*uDUz&C!xBWM4C+Q>bZEFzrcM6JQdpZVc6IBT5@#`&X1U#6}A!ZGdz-ZB-vyDO6bx zlw21|>jGt?DXjyPz9LBS)dthrREg1+TSzIyUyj>GDUG)FRJ4zU;g-~-t!TKVvoRu(JwT}wZMRcX8f~fRP^zTC)}G*1DaB~Zv6MQ|HV35`ZRrOt+2=%C+nG+Z ztxhRMTlN^G7;V`$^awQC(j(Xd;{;o}^nEng(t|MPY3%}XgpTGE$NC(=HnN1&0Go?#}c#6Zh^NtGCA!>AGiEw`Lf476+-N}WJUZ_hp& zXleTvREcqxvlq1!XYF}R<18H_LT?K`477xXXP*;ji&N?Z+QPIi473@j5(6!3&n+j= z&Y~0pEpv&LFwW9whGCZ5&&n8O>7VtSe70eBP>G$4hC3`PHT*> zoWaT%UfDUUjM0^$a|?qjw~3W8wz5xI8AB`inUyiJ5~{<%N*1#cjjPlt^zMaGl^e^- zPEei3${15wdM~FT)%Hy#R(3)vT?!acNjp}?fJ(};^an3kDDH)TK0l(w;f$QW`*QMsGeCK-oL2jPa9AVr2}U+;vu_(UaQIy}2@t zo;KUX${0Nv>Shd{^k-7piJj+J8AB(R!pbyq+AW5$5{;bH%{^Ed<0f~4l`(8`!&#X| zO=?D}u`)(YQk9i4Xfm%@iN;LYl&YMFN$-Xi|p)&+SB>NjHV}xYsM0Wz@FjjWr<2qJ$!sAL-qS29_3cYV(Y~;?evJ)D^ zS=ot#ziull`$+bZ&PI&w|`{Fs$! z{G$yrvNDZ+_8DDaWheU4=|Y2_eIyz^G58TWOELD5Ev$^8kGx@J8u{#VE6U0k`3OBX z8u)0(jad72e|9IQSs9`pePpUK&CcpCJ0+INQj^P4S#iQ(Dl3eyOl7q(BdPo_r5cs_ zlXxmK#n-aG7Er7NDw(SEZvfo3&RK`(-~L-|&9x?53C2EQow!b$VRo^~^40mpdLeVU zI#cXy%#&^jOEu9dCLb5anDedeY9XztnA5z&UsES@Zj^(mUj>xcLh>4MzGdZnz>*YMHCbn`Q%fxKVNDD_f@ zTkE;u!W1Qx+a@GcuwEVx3D6F;#za_sIg2sXin5~OE0;ZR%@X@UtZ{`#xUMWQhf9i{NjH>Pd^UNEHCMU8EmCut+l6y< zpL?oKPw#L>#?W#w`vMk&fI4*QWrIYJW|*vJm*@d(Mo@- zrWq%-ku~m!*i+vsZ?{g1Usy+!(aQHmXQ>f)#>l3w)~jm=l;e5@@wpUcywsA!CE7h{ zD*w6FNZHR7rGG&phY(?u(T18IDCv}*e7Ln&_}%(JoW(^evJoug=O=3$jkLyPv4C+} z=hb`CSos0{jk}goLAxeo=klsY_-EQV`o4yIQYCev%ILS$2K+WDL}(>FqTdz7D#fj# zN`&~pe5I|Rzwke1pYnUek1yR}*Hu@~FhF-#0ufJgL>c7k9 zj3b(5{HXmZUzbK{JJmOGkh(`&#;?)l3k|fgMy&XWOw67_s5D1!&P}rV8F~48_B5vR znJx?(JgSf`$jCm@85#E(iEvmO(#fgaNWzgZGf78>jU^rIbw+ckNqeV3X3`EByPC9h zSSV?OjC@C0J9Y`Al~dc2v~+5}B`qA?CDPo{ohQv4mXS2YgLNTIP{my)jh)J+q!BWf z<_bHyI;4Taa+CVl)FkyBvo55r!xBjyhvgu(odyR$W4DOpckIrQd=5KA@;a;>2}H*3AbF6H zbtJ%HD@kr-%y%T0!w!?2PHh&F!_j?8vg2ihl59u+MbaQ+t`G|k){>Zx*<)fjmFtP_ut`Ki#*HDWW0#94 z$jBZ057^l2s3MV^%1C1W;TpO?$sr;*tRmsjtPbH&#hoOq)8K;rcWYp3%_a<*~!XpV|R~v>98lv3x{=Jo+D#cFh4si zk$HxUE5kf>SbpXSGIj>@lfytQT_|8FQ4mg^Ya8+;mt7bHk~<#azdOeZX8p6<40Q>X9X5qIg=RgO z?~q8$w^+wEVoo|OUoj^f`vuH#hfQaWVFQIZiYl%Pa|9V1%^XHX1m+N$)n>kN%oZ|V zJ8T^Rw84*XI3ENf|=zG8^kO_#x`V@I*etO;E6V7 z7Nd#;F^f>uhgpc^BW8iqMM-8pszxyLki;-^ovvClbDSR5GqW9*mYL<$R%brP1|6B1 zNXj!au#O92K69+bG1HMT7no^I?H=Y+hh<@=I(B)PDac4)<`YNvjG2rbXJRHf4ZdV1 zqAHA;;HZ`}A3OGGnDNM%wahp)Yr%|l%#Jc+kdbI+w8NS)DGr;&Bs;7)ljN|%Od@uY zfk{9WyPJu3*eu3}jJd>&qMz>R$Nc;wsk#05uz!R@8VpCm-X7*?b`EtklZGH+J|FB< zy-~r9jX}ar>*bJ%A33&_ zdO9{2dpMeQ(GDrw9SJk1n`1K|${`IS9h;L~9h>D{oT|~CovKz5PSuZ{kg!qVPSv=M zPSrL0bDQn3KJA^Vj_n*xv9=D$)y5%{T07+PRt`yQiG;n=!XZh`ovQ229L-hx)0+L{ zwW|pdW>aIwhH2zz&Np;4DGiXY!|J08d(?AkPSkbC@H$9Hwb~A;S_=vDswOt78s^l@ zsDXqVAL?lKS4WdP)ts7TA0lC&*dNX8^L$hlO>$OoOx9O+YGzh)NX?2!$hHbdm~-Wk za6QU7B(SVgHPQY+W_SEsX@~4Dg@g<%>Da6bamdONc<=(nQNf)phJ+0-iiG?a?AWd@ z;%H_Ub~JB-uyun%h-C|61)HaUL%9473Co9sOwEgg*&67O8F{dEs{mARt#do1UM?i$ zc20*}%i)mA*&R|h8xl5MR_vx$7R17t5d$(g25mDsWMKxU?XvU^`8XZgW=x9;?n)Ym z*pK7oVciStZ*np%mBA7@X1_UuDTyU!wpxIeibIL#>heoyDHuz1Csrse6~+?tvtU2^ zyPrlsWh>H>dkseQHgl`2Y#laBHB|m*xaSTH#h3tC`ins%2HPDp_T$;#QEA*UD*Sw$jr40Kxp- zd~H6XIRbag>*huC49ycbM4x28G`E`T&6Vb2bFMkVoML`#rqGOmp=N)xw;64AF*}&8 z%qC_%GtB(JtUz-Iikbz@JT%unqiNBd&z$j=<`6tF9vHWct2B?`lyS`X+So&L2{s#R zjpfEdW41Bfm~4!ryTwNugN<04RnX0dFxnX{j7CNsBh;v3lrut%B1V3iUy#*EZy1JT zF#2o#ss2E}rC-+1(maE2^ga4^eWSjLek(9XpRP}$*#`0YP(4oXrAO)EdTYIjUYF(^ zRMyMr#q@%DfSy%Pr)xS-GY?*9kF~qnb?t(7N;|3@&~|BCwRPHZZGrZ=HdXtW<{*sJ z25Eh@9$FWzz1Bi&sMVsG2oa7^%wP-`cS>CUQy4fC)GnVD`AJa zNnNcjp}Q46qx1LdUzu~7k?D* zh}XpP;&1{A!1=M zub4y3D4L=qGQw-&sqjF!C0rKH3MYhbggrDbVxzE%W=6~rrVEpVF+#jBREQIL2~k40 z&{}9B)D=R7%0d~MB~efa5V8vC1daZ!&A0pu{xN@-zs_IaPtor{4)DA9t^7KEIlqAa zoS({n%qR0B`9XYNz6alhZ%=b48uGRH5BUmwNxlf5kI%_x;w_%W2JRQ`8TXL8&0XQn zaVNP$++LbXvB_Sjkj3Msc4NY!llV-wsBbd-dj-&Dzf|Hte1WShz4k3Jl@HN6g zgaZgp{?$G#?M2vwup8kkgk1<*@30)2wxyz_7}l4D>t!eGuC0+8rg`Y4G5S+ z<|Y}hMdcaDwiNEMp%U4BxNnY(tLz@2y+qUAYe)vnS~`x z%OjZG=48l`&#-Pf!Zd_W5irG!Ou^D82$K;o)z8VgCKFKkF@lqIhk1i!EGow!IEjXs z+((j8nS_7|gCqe<@d!SIQ3xXuMj#AFz=S(86iY)8FcX$wh8Y=%$^i)d5#kVH5&9wY zMd*Xj8zBav7XqfMk)BxUfe?+*9ibaS6hb6ISA;GIoe?4sIw6E3bVTTY&>o>30;cAX zHdtzn&3XAk;^whfo)x4nl2&S_m}}!Vqd8gd$W& zsD|(%!UqUd5vm|mMyP~P5upM?d4zHZWf96Cltw6pP!b^op#(y4gklIq5rPqlAQVOj zLMVh#5TO7n? z(jZs}CW3*WBWMUJf`T9;NC+Z=fWRYg2rL3YU;vok5q?8>i|_{FSA<^>UL(9hc!}@= z;W@(32+t6nB0NF(3E?rqj|h(t9wIzIxQ}oT;V!}*gdY%YBiur`iEsnqI>I%Cs|Z&R zE+bq*xQK88;XK0k2_OO#@D;)?gf9_xBJ4oej_?J-HiWGRTM#xQY(m(GumNE`!a9Vt2x|~l zBdkJLiLe4;Il?l8r3gz979%V|SctFyVLrk;;xF} zz%f03DDWX52ZN-Ws_B>713?Y|*&k#a$XJm5K;p(2+`Q2nG%+B1fu!5AX+O9(rU!8P zssgIPU4Gqw)1CZO6A7{_$Sxr1>m8^*0%RwUbTd2E;BJo&z}thQ?-8JSy1|@E+|SY) zINh;MH7!B50NEU5GmuR|;-^m688?Yo4{=+D88lz+FAPnk0OuJmuG!ova>a zm~=y4qfa;Y@Gq>c;x5TBMDwL~RPJLIRu)MY^+2nm+EJcpJ`plnhqZn}uwG5BU=^hA z@6V?l5L46#Vi3RFBx+i1v^Gcc(cJk4>LPvteNVs=#o!Z!&iqi}F!!@LU2d#j5)4(5 zPO0U%hgN{FSKn<^QHx6n{Bfg=BADCFOUg~Xg1J$yZCv4QSy|O1QWxcvv`wBJ|Jeb>dn{^=jUHmPvTpOdHBamMA1`3~Ofx>mZoj8H( zrOuKT8t3$p=3;fKP?7HHAEI6q&j@3!lX4#Gp*)AaO>w+&Qi>Jd@(1;m#tyTM{Gr)Y zzprabd$m9P3kbK>jrwu={=f6uGpU~bwK11FYEDve((hH88lUlJwTV&=C8PAYvQi4B z?*~|E%}`Itm$^@^()uE!9=G2rDs14JN%@3+YE8Zp7b%}H$8aAj!;GRDXjpRQu>(Sl%v-u)slzvm_q4rXm>l^fJ z%0=yg5+`NQXG>G`+NLB=Q+AkNOXHM}=-(?SVSQp8l0vyRj6{=Lxji5w$x0j9ZDa+Y zLY9MmGFb)~N0tKiBufD6ki~#2$s!xsAhOU#5=<5VW+iZQ7Rfv?YeD7$<{@)zWNVPw zV77qFvXR+BJ_r1a%mf@qX4uHxA)ndEW+Kx8OOa_dlI!GCz%{rRsvVhP7r9mB6Ts84mgqWSEWQG#Lt5n+yTmKnB~v zuMinz7uiu{AgG6u0X8z9kp6acLlOt-QzRDDFGxRI-2}Hq3Z##%Wxpo9!RjH20sM*d z0!$1C4$1YL{taQ5;TKf2@eiC{T+i3kcG5wwGc2on55upGQY1ch&i*erO4 zU@`cGpc%YEkl+&{C_F;29Q;89g*S*;4SYe+44xop20suac!7vw@BtC3@BqPT;QxUH z?+;N7z8|Q9=LZt}J_Lo=2NHZf1ck>368t>`g|`QmgRch?JUx)$=OHM(JdohyAt*dN zkl^1TD7-t6;M;)&&kiK`bqETt4zYXS(;-yh(SgO_&w&JQ4zz|i zHLy1LX<#XMX&}KzLllFDh5-0y2&?eUz}n!OfwjOh0||Z^q8fN*2vzuG2nvr3LE(=f zioqKLi@_HI37!~O4g4^$8hBxdYIzf%q8U6ekl=rT1n&z`489ja6`mJJ@VgKcUKdF4 zxeyc{7g!Gd7J|at0tvnrVl&`rfhzb}2nsI?LE&S81P=?egMS4QyekBSZv_%OE0Ew< zAt<~mkl<4xC_E|zg+B!nyeR~QF9i}jDa3AoAB9kb7llxVCGf2qJScD_@ShM?@SYF= z-w9k5JST7+@S8w_*96+ZXF^m1j|lOXM+IvH6Z2;!WwlR$Fd^>QG0j* zqGo>q@MXZVF?|uO`(W9D-dMK$F*Htp6pfvFq482r0q|Wwv}`uxwRT#0q?~ zFMX#_z_UOA{0aoXs{m`fTLHCK%A@u-zTdaP_xqYnam z%kNZFDBwGQc%ZNV_zfUV6h!<`0MR190Qd`tW7Z5`_RHhTe$hNw&^0$=aW1UAEGJ?X zzVPSHE)?(%Ks1Qpy}UH`nK9OCp}-GlY!u55VrA?!!`YW0SHL>g+-xAzJ@{!o2gn7n zL;Y@72N@rC_(<&x$W?LOt9mj3MGOB)!2h@ZyL|+1AA#FP;Pw%?eFSbFf!jym_7S*! z1a2RJ+ehH`5x9K>{x>}W$tejp!CU1{6@s^FwkC0Z^NfMDUfs~6h+FqU^IWB(f~k@} zpS@6c+Kl{1^nV*x+yCdYKfRHdK7G1%`r4+AZ>%ZTyXyyET}c;jF!%R*e8o112xn(sF`@Z{JdQ$N}@OTSicdt|#5oq2O|)+8 zQ!~05U1Ep$3S$ez06ZFfZN44tG2Q4Bz7S>utb|N+uW&n>7tM?^qVdtNs9*F*)FEma zHG+%)RrOo!^Qdf8B+3&7kskgPUNzQ*=k?OziSXO-3q7ZC)94<41{nh058u{Z-Z6Zg zHwqVobHb_NB;#~ADttVAH0+{RfouWI_?EC9pBB~#D~2W5bq=8$!~9|P&<_3$ZW!(L z$wmXnA8A&+mk6gYWu_{e}J< zKNIHs5BK}|-TjCB`~CZ1wtoe`82{4G>jyrEtb&)lpS&a9m*(@{PQwJh`Y*lrVW$5g z;{|WNH`7acWBE;QFwFDs;GW|Pb{;f8a+>MWjQq~M zPIdhyryO{^pNAZKRh`@r`OkAS`zFjdIAtHU_p=}E&-6z2CVMr^@_)sCPXEMy%ATS> zV~@5UHxv4LnCIV#pSD{;Xw!nmc>8YqPU9E5l->&F`e(N-zSg>BT{GXe&YNYd<7S3+ zkoU0mTAx_!^-O?3S^@c-^XC z72`RrynG~PI{3r9Z2n{(F|z5$P2u}uCHq303<#FPtRk_%NYHt9E5nbCysc|eMM=B{#EcJ?ojKZ;$RjXobY8eY^PbOH%!%8R{pJYJZc1;uVtW^r(GnugZqUk~+V4#ujy+Gt_Tq6pss&1C`W6 zN$N;uw2EyR>Qt#h*`{8Kq)V{%Qjin6LRFVy2Te66f%viEXfc;ZDpBx(( zCR@i+#=}bLtfWp#>Zqg+N>ZmVL$$|@2UTesC1w3a))nM_RoYxhsxxOaQ8x9bWn}fB z`l_^^lClnqI#e0z+-BUZ>SbMYYARdSRVVAbRaK>`F=k|S@QSLmyprO+Ze^5Jw$e%} zsU+3TGKwmjI&B$+l&zqW@+s*KC8?`bMs8)xsiYiAim%UBMmA-OloTk*Q<7}YkX^%3 zHra;2W+|I$U>WM#o)NcfD?^LxB_}IsqLQ9a(l{lJQPOB7jZ)GGB@I>55G6gXqyb9m zucUrT>Z7FIN_tF5k1DB`l6orX5hZm~lIklBypi z)wM9GdQ?*NsHD2KCDpYpscyPS)ytBqmnBs%OR8R$RJ|;z?ls9q>hDnfEvfojQuVi_ zx;7?N-%8e2>#AOsRJ|;zdRg)=RZsP@q`FQfRewunU9VKnOR8IVQuVx~>Ul}k^OCCP zB}=JaQ++U5T-j6~Ocqu))d!QR4<-w!Qgz`^s(zVN{W6(X)l>a4nM>JJ4^65bnp8bB zsd{Kq_0VLf)>VBq=_{M+wMo@$ld9JyRj*B|UYpd_x=cw~eKir=RHs#aR(09&aUIol z$Ho@bg;ft$9a#0>;c?|*v7kD%>d*1b5azJTd*Q%XP`$cuY*GDJbzjwI)nkF`yQ=G| zo~t^p>aD7?s=lhas_Khv<6l>OvUO}xeN%Nl)%TjkbyUw&-K}9Q}{Li|SvhQ>i|s zx|Hfssza&%q`Fg{c!R22sa~ZzmFiQfBdLC*x{>Nds_V%9!Y$cJxMk9Bamyeem9%H$`APUowR>}?{H6gz4`BY9yh<~=Ipxs_pV^~=sNV^xsDc*}U%c-HvN_}Q#$R&%;R z*1E!GZ~q_jD8zW%Wo|Lonr}mXyXVcPy|!kCmtl^x?(nzyclaZ$x!%#BwfB`Z)tYFH zu==~tLiD)yR*Rst`-N5Cs_E^5OnTdF+xo}4Ze8%FS|^-0?9$Ez_bt1ieUIDH9%Mgi zclJYjhW(_y%-LhVY9Fw7K$N@R?Mo07ub%re+rUn+LtbIFFS_BqEqo^QW5x|*yYQSa zyZSFi+nsN~e_|En5uEOC@Z0NqtV6o*SB&1`o1>SliqW(B)o8kNj?arG+l~E0(I|bk z_nMc82J$sg&!{7OQ)%dpi)wpyE#2|Gr@bw94!@>1#Q4zsHE7Id=tZN-`dxNwqjpp> z%J2Q@PWH~2e?q4E4NiG~t~<)i6=jRe@Nc_Hcr`qyf9$_$4hfH$Z6SL9hv7Hj9=$xj zWV+$U;Rk^oz7yONz5!oiW*er_(f9)5BBa9!4hx5ceVsgEx3FDMMgKc&?%c%-hV`6b zdfIu%HT~lLd*0gcF22mI?G6mfheh>o!aSkxG8R}Y?QdjAex=g3kbvjesC^E8asy}6_n@ncq zxQnLMWjjM%iZj%;I73~GGrGrXWQD>ZNyt!ltF*cm zrPUQEtu6v7*`MLTR^8|_)O{pVJy@jGg+ImPU&~at;k3H>rqt~Sgm`-? zb(7EN9oy2`mDFEJ>Nc8ESK(Avy{t3cN3C&JETz-9 zQBq?iHByp#T*}D0`V3U1S$(jsvZ+3uF(|gBRo_afr@&N)xHQ#XNe?M0>+F|PHr0w# zU1D20YoAqHORI;Pbk;di*R_tmC52 zQA(Y`%&cqT2=!~~2&L4~O{+aktNxKzdzGo$efr*bE9tD?RadT*`inAE3rndZnORb; ztLmk?#ME6?u5hWWo-7~pn3XCLuaQ=3WU6aPX4bW2 zSX?hd-DgrpY|B*rJXJ8ZrPW<6rJik5>N=UqAJeo`u;x$r* zmGrQZa>P=)RV-y@wf2^=Ev?5=D(h%u9lszhO{;5hhAK@@ic3=?V<|(O=}fgoTAkHQ zE3TKS6-${}*Gcu{kyh8!v^oo^yW=&|>NuyTC|lN%R7X6m_9~tA8*Sn>QVo@)uH$L- z8<}QYnpS(7R!1%MXk41gYVE^gTiR2S>J=I4SY^7ZH0x;G5!*7e&O*J|mQvS^RL9ts z&MM712I?xBRy{eb{*IKo5@u%gU)3fv)!jKmZ7);pRZ8{Mbk^0eSiCP8s#l~Qh;8Y9 zO6sJf%CVGc5KEcrOlPXQc&6HNW)W3SweieC%BFf*raJqX`BZ7v-=>aVrg~P%%%b-7Q}wb;)j!gc<9g|dN_s*`>iUu%r);B?q^_rFbp=jmU5EOpdg|JqRxK>8?hEN2 zs$N$mbx~4hC1te|bq7m7q)Jr}NVitD7D{TWq$WyIcipt=Gih}O(=}DS8cI^n8)32A zRlU(ta^d~Iwmvoy{T}@qosEt~2cvz_r#J&(8RP(b9`e|wqbH&f(ExZm?G&|+nnd?T zcSRMV;!!?$)3m~W!W$6b|8#g1-Y)ls+ro_y+kbiZTKGaZTa53=qx)iPKdcwlglPUH zA(mdw&y7t{aO^D8a-Mi?W^bUImyqyr4f1US^x7d5pd&ZmQP4Y&11H2v(k^cd&fmh3` zQP!N1sD=+1Sgx|7_IFfQNI?dZ028^Zf=Ww#{6 z-OuKl&fm^8h`E2lIRp{+KXo=btDLtW+Wxc7Q_d7;v@^(g)amTBb{a#J{VGmrry%6t zv+aK%zWxRKgnh`~XK%ANKxF+l?HBF2_EdYKJpy9tKVrAHTiEsOnsz0-gq`2c2C?-2 zwys*|tYZ*IAIH^cR2|0Dr&*IAe*OTfhtm+WkS^atLAz0xOosF z=5I4MnD3cyLbUw3=2UZ{Il}C3J_2#_TbT9Dnr20_xOoRe$k&ZOjbDwk5FP((W4E!@ zSSQBiAuj$bBV~*?h8lg0han<3$3V&sNc_?muG|3yCqaqti5JM_)^8hx4m zDn!7ap(pjR`e2BD-$ie$H`VLv)%9|E5r}*5>x}=-FY(j-dx&_ygMY-|=gavbKA+Fz zNj{bj=8y3%ybW){>+)*6EHBJ+a}Q?j{LU`1(~u|qfCdw9w55<*4C1cI92dP9lTuGj zrtH#saG8umd*oqRsIq-&-MCB`qdh!87Ix)v#*8l9m$uH@Ihop{Q!i;!x!AN0tgW=E zWJ_9BVxV?hW+>7i@|mpHmamsm8`efjt+hCvS1Wcw+FG*nQhI>BBX^)herd|e3kb39 zq^%jdE+q)GB&8-yCI`_P^E%Sjh%zRgm?ruv=34l^2lGWj0Ly#3gy=6Tfgn7XHjFDP<*YyTGnW+j;f` zBnM&+j}!hS&a!#3>Y;B&skimvKuD$YVjVcB$r6I9}JuPh?ad~M@Y}VpTe4F?gS^6Q1rHxEp%@P~5 zU*zFiAOC3(l2xv-j^&VrYvm;;@jm-P7Ovs)GMs?;in7|OsP5`=Y? z62z~S(p3JvlrnkTcU9idM2gA#d{#Q$M3VKEYdooa3%dvDQm3#RButJ|C{E%cDG(Gv zEL;*GBq9kDxX9iHMdO)xt^r{j6B+VA7|TS~IuIZ%B?+UoA4z~9VkC^@;>rbeMzB#N zsEnQvn2;<(*^4AVG(r*v^JyeN)It&5iiiF;54G9ormIR2^NJ1|@hJ>E1CJ7KKmIRe2vO7CXmTua95+GtR2@s-{gf3hp zje{TUtbIXyiV5Ms0D(-Y1R@!d(4KP=9^xXm9n@*Z{w4uJ0Fxl1I6@I5W0y&8Ma*C{ zv?@zAT5^&45LSMG%fF=sTTK>-a!P`TkBKWayMqi(`LiTI)I|~+^Cw7934j|ik!lV$ z+<NPs}QB!~dMxWnc1 zlA#>$K>~zmCZP;FM^#GSNyNOE@Iw#`mTHt>0SO}1EpDw?10rVUgQ`W?a;j07=b;kJ zhX^GFwJTHtfu%`+_^2d6AY~Hn(B%H-O?*lg5h4?R2vhpOW+0p;mE_b;kdQeJKxk?bbas{m z&Zm&T*fQ89GFTW%~e<{LAOq%jt< zo;_xCHQE|YA>&y!qpVTb$YsFiKFD?Uvwl)PtnY_BXPe|3-h6##{D!BZ3hak$f}8%E zr~;iJdtjsJZpa*1Dk=b318w-8a6P;LGY1ZZ``~NBhVZ@c&G5x=E__Rv7>#SUao?UlIzy+yDzQ0$vL)1Sf(+_#I&*ue*kme!r4_ zem@(0J^0(Z3Yq!7Ox>w#RYUQzfi8Y1DW<-HRqW#%qJns-eB`Fvy0gVGVFoh26}DeGF`o^e9NzkNQ7Jvm4Vt}Cf`QbQ@p0EH=Qk!(lj1QX(~Gc z9Y{=~$&`;a3}Ttf!jyJcN*Qd0l#*P&B8lko(l&+tA*HPB;FGva6bgY@WueO0%qFlV z(gx9r|rSl<}>Bb4l<cO6m5;nF;bp{QvLc z41=KT@*MW&3#0@A{H3Il!?Rxe1!;po=<;0ms3c8~F!_qey0cu;){T{w(!)$9B4%B4 zOB00WmXb;k&N}f8(gx`Xq||}$k`jcbml6cCmr^@sOX)!--zivI7GD)qlKKCV*}N66 zCD&-l-;vS-+CNf)(C$*YU;AB3&G|VgHDlt=Ev^c2_GH#Xez+0YH)JhHxgaG7)GZ}Q z4j`ra%#zZ5yqT2hG5O}nAp3)~)zwx>sSYb6rF*o4Qo5V{Af?*8sg!Cl`5M9?F@m%~ zxPK|##VSh)@_k6D8hczyklR2?RoGxD-O1)isWO*ef>>C=LW&3}K^6chL9ziU6=L$8 zj6uc;X)C~sNGU&yPia2ZRc`o>_==yGACuJ}FN!S8txHobHd{(L`4Cx{BcC*7XZJ}- zrH*HjwpZF9cZHM!HdRWHd_hVcldq!8W%8R8gETR+)Mjxnv{>8=O#Y)RHF$it(0Nt4 zQ@oTcgs}5c()cJTLDCMAw13$~DgC3#qbLH~(?+zvJz4k{ll@Hllf||_SYufV$pNGU z`8uR@gWZ%8#MhUbyDtAQ?HbQ1O;?A@T9A@KN{~lIN|)mV-w@DSnjlEOlpyVjlrA#a zku}I+A#IT9K}wMGLrRc>LP}@&c=`K36_TdYOg1O&l=io@on*32ii|jN1(hTGcpz&X z_Q|(b%xQ)riseQuZGxjlGAxpQiiBj6aK9SN#On$r3HuDqG zwu#A3sePzjlD3UZy!MI1z2Rr`KY0Kns8;3kg(w1VcNhyQJT_(w^NE;-Dk zNZSZ5Z@41ohO`aiA4zE_FDRuUJU(WFS$=67#0E;~aUO4PpceNYNV_5bjQ&hk>!+QU zQePHdpZjPZOIvU4FIVrNt>+68(Vvj>?^1LQVgMeE4n+r|-O)C9V_yq70p9{YfCbSr z(Tpe+O^ilILtyrQ&!|h(E_xtp4Bh}W;qAR_R4mFL<&6BufH(M?;jiKO@ML%dd;&fX zcZ6HvJ$`lgcDOivIh?Q82hV_M;gjKbzAYR9bN_pX-T6=OR^KLU9ySQ?39G~0|59P$ zFmISGbV3&VrI!q@!>s=^!SUd`;LBhS_zG+e*1@d*rNL{#LVgV9{XY#J1CxWX!LVRJ z@M!Qb%=m8=GzG7L+WOr=)u4P(A}FZaFz-JMtRSHm^8fI!`WOAvdNuGNIOOm5cln?A zANue6@Aymf3jQL00elIV!54uy!9;(wKZJh^F#vk{UHo?ZXTK$Q71Z-<`BlKLpoCx0 z&&@aap>O#KhzD>LybDfyKX`|{{oXG4M(`oT19-=K%Uk3v@SfoZASOT>{0zqEnm5$z z@AU%j_OqFdtf1I1nui&9@(mCoJbiQzQ!aR9H zRp96C8|o%@}JP94Y(SlKD#6m{}}&jLILz?X*`_GSB={iFT8{WW+ld}eQf?+>f& z<@Ou)OZGf_7WgkrvB!f4!{c^uySv@dZeusM8`$^Q)$NLQDZ8+p7d#mpn}ILGZ`LL2 ztaZXVY<+F*vp$2b66>v1)^h6&>m_TRH4El-Oo49`!>xhVV^%ltY-kPNC+b^wTh*+J zRw=8nl^6UQ9QaD{mwDa%#XJK(4&RwyntRMo;akNz@N-xS^F|h$&zdvMv^mKfV-5vx zhhAn^^C7dP*~F}8)-tP@<-q5mfSJn-Ow;(+_#JXGUV!gnM~#CpLt>}#v9Zxu1KtmB z8n1%?!(8JjBg1&Y7zJO*`Wiiq&fo>n!e|6v$!ZvtjWR}2BcG82d?9rG7JMtate?|= z1b>LH;cM9zeZ9U)Uk)>3UIL$pS^89ciat&st`7vih;BM$b%D%g-roX+e8?K2v1&eK z4bfP6AF_sMtc(v?Lo`;*&mj=_kTyhPReeYs0=Y{4NT9GE3gq$wfzbB_iuj&DMc)-D z=Q{$Weay>shi?h3vTq7h@(qD}zAlj6=K=+NCXnCP1oHX`0pI&qpo;g8z@6SLn4~dz zyo38WUf(N>Fft)9&c^&D<2e3dFiIheVfDWlw0PG=wF*g1wV{}MOgGsdnM!S$t z2VmmHU?f9mqnsrO>um(XBQeq*CA5+HSOgy?Fv5XEIslHp2csQA8*byz4mXyfM)|RZ zIa7o-j9*1idZwYy3ZV_v=OfsQ0z(|!-63W})DnvX279j{jPnRSO<<6H17RINB;F8s z+{VA?acF=-qr6)Ko%TW-XkwCJk(@_^(Jjm_Hb8HI8h=utzr7G)u0l|D#C~=Sq4m@I zA$S>qzAm1{zQ#J#w9f?kI9Cw*;|Pvh>22fQ_SQ3m5k_SM9&>jh^f3tSL4il@4hRd| z7mU^jtrsL670};Ba9p9M2RU{$*3-Z;7{w7<4;OMP0dzczFrp*0N9>OgIyO(4)2h3> zOBlNw#S!eZKsTEq^mh>2eu0M}si=Ul8KFHV(AB|Bch&DeO@k~!kO$eDfza__!N`!% zIy+ks;1ne^?F)fU7B+%TI;Ia6Nq`{no!17TV?`J}5?Tj)HNv=sNZ=2&cd%xAeVH)A zsFJ`#UU!6148f}lv~%Vmj7|uKUF1Qpme3wFAx)A>KH1iV)J%{>4CcZKB(VLrfw^Cx zwJ|ZPa~oSItsO`iB{DYS55VY@&|29I5eEJ}t;!0er8@y(VKanLE1^B$wMOVm5ggBN z3l}%p!oVf~qgkTtes40u%8k(Qk8Ey#EwtuVGeiP+wwa56T{H6%rA87(O7H ze^#K0-4$UCMt`q1ATRBdiw?3GDc_9K4p)vT&zs z@x`L7rhO7&V5)DC&({e)K4yM(aYAq?Dg7zY$u74Im*u7=Pv5v-2Do!&l#gH7{J9h)YM77Al! z?;^tb387s7I2L7=y%qX-9-UWnn24RdtX!Qij zdwUQL!8j?-Z~45!R?fqHDCfM5nt_)I7@rhcS??wRukvNRw@@>_MQHc~WxU-&D`R8- zC}ZtMjp0Em?d=j;X%F|Iw3#7(Rb(=Q8@P9bPy(TwqDZ8}gY1{y4Z=Z$MVi$LwZy*y zCA?n=*!)U3y;0NgXJD9AXvIA|(8ax-q~YN!4nClwOv@)w%sY&*(+J}?u@5528)Q58 zek1&Xu>M4t*F+7G;S8QKysr>8wu2)2Db(0C6dF=cL!?bM+ohTKmiYTp@4@6wt$P*i~>4tStbk44?()1W`6q(g#H;K zaY!Jahb=6h8=?kIvBIcz6S%{}9(sp6A2kF2E*K0JT3(oXE8yV1=QVI+c@v)sV;=7^ z;X6WK1dNJ_vcz10+}?G<0fd7W-P~qJ@i&V!aPa8n9YfeH5ge~uFw88poE~a9Jv_EK z-6R@~wFnI_SUJ3FLd)S{lg#1Z0nY)sxrI@~Lzdmci$!*?gD_?{+922}fo$GY0^Xpr z+09Y2RwCF~fyn!vupi;#5s1tWP}8Oggx(hj8?SAlg*yR*-ohAoc-;#eY*vAR-3rFR zMVas6-{9M2QM2$O1w-RP^E^E5o`+W=&%s~!Ogv^VXfCR_9<~$L#Y>$Fq1S|w;puZA zAw6i0gPV5rj;JN@f@FK!g=Rb55iki}XdL^3<>9Hdyg9;X>G-$90KCvlZyUnJUocHP ztS}}ojE46Kp)o?oh77~>LessE33!3gP3({`axaYB+lp|pop2qS5)9@GBlET(96Yzo z!sZF%`v%1Og*bGubng~R^AoYub{pJg-e$3MyNRVaNGxIe-|wK!;&mf_(6XH;-Jap! z;cwxk@GOkL9}d3)kN)l9N8tzI%5Yiuy7`^`tJBth4}9%kgXnnA`ZN7BjFyk_hl0O- zFTZP04?OpOfVlxBU@SjpuZf%&NOGeYq>?-X6{gTj$bOM0sj1@f#4{6c=^@S*U5uu)hCzb%vu z-h~+mxk5kGgTKX>1^-?*@>}^$^tyfxzR%AM!2###x_R8HWtXx4g?HU%&V4Y}J_p{B zk2{y$GTv|AFWyhQw_A(9!#DHOdZ1Ty`suy(Zo%hz2R-fP)nC@fa33*^ynu?q~O~JK1L)%gGC|0{eQ8z+8lmUh816*VL=$PJyohcRK$< zq`)H1w_aYDgu=MCLmY5Zs$wq7v4GWNJH7~6~w?Pldn)G`23oJNI378>6C8(%EVhgpcY&`9wa-`ZxL`x?&HF&g=WU z!O@S=Ve{YU%V>AdB>F`E8yfz`XtlK{S{}Xbwuu%-&jdd~&WogNL6pnkUS>2F4%ru+;UmMiHNf`Mv$8^?^?U>R~ziu`{J2)jGzqc_9b^^RL$*xyHE`vE8B5Cd{ z2h%AhUNh)QI|{rxgk2^chdjh*PzQHMvKyEf8M0E-e!gzMq>G%RiRF)BIXEgJe}}mw zVmte+&dB9DCWTIbS1js_++xVt4jqr#`iHcg6|{eE>ld*c98Yx==9qA;g5Q}_1J$kO zMC1zinj?NE1z&Ik-+`|>NInQDbBZVCID4s{i)p|iMRjZXy>~GODkSG_f#vU7dyq0A zbOPLC$?jxF&NVPWG9>w?{WxY}PE<&jT@lM=8t??TZliwD{F*2;LtoI_p$=}`$nysM zE;w@=9CBixL+e=+Ga?IzZq&~h!-hK&2zy57)G#@g5BWin)2lXOEMqS|>vpnkHGLHOO zrzR?sZYRDnhM_&qaSzVps2|X4BIi06ko)aE$fI^|B)Es$;^sKPq=PQ{t74N7RQeNvJ=gH%7i^y-d3MF%o>Ok>FR1e8Z%s^OKzyb#Q4%?l-9` zgJV1DUl{az;NFh<=f*A~qx#R9)KykmwB2v*Oe{ZcPeER@sZp%7F;{n-Dn7B-d=$&U zF&??cd>9E%@yKT#+TL!XHvRppsEMDqPhmKAUHsy@JVSj@2wKOW1qtFJi&3%zQx#z z1b22MxZEQ@cTCc)oyd?U!ObBuI{+V@w^KGfGcyOH3pj(pX56}iIMOm>sjgGm9XgV#NBsYB;^ zKEFlv-0jHg&NSqw&XagN;Cn6V^Q}`z@Q_D->2yVY;842&zkVt=wjfV9^^nh-pCV5= zw4GxP9q*5vqGFw=J*X&z{QRlg6p z2;4-)e#4}X@Zp4^iIzPD23G{w4S~rHzR6Fv7NC~Em!nA@ex{jZRS-s)wCg(B(x{YU-5S!!8wu9{>*gCpgz-hkEqdEpW)Gd%`lgs9p-T$pK_^JJf%NK zx;7m--J`43bbS!%+Cbzqn|jSOeKzUZH2jt_#ipyx6>mA(w|dKvr@hyZ=e$M89UdLu zbsnAHBi>VJKj0;iTf7NKm;-`5?2Sb3@rB#;b$;&Z~@k+bfU! z!7GV;()dyleZ?(JG^p9Y#2nOLcIo2tvPnM=({#}Ok^!%~aQ0sskLrt_ zO*E-5!PFhp7rLJi%{fu^L{kkW3&Lp7lNwAF1dh`j!x7~gOc(^cKTJ$ysKc~D&^v3E zj;&Kn9t6F)W{Oh|6I=xTUm8-v{mn4T5w!-$q%)OhP%ncyI;cPCK&0bDEtqZysFL{0 zq+1nCIt0CF;!lH47EC<^JzL@rgKk|g0TGYh?@e(lH;slwz7KiBxIols_210xsKbOr ze|N7J@w7O75651?Ee%@aPQF{3U)X%o!9*1K{tPkdzbyH-pU`Y7cnw|+Qw*Om6y_+ z*&iF6CC#+^Ec>y%}QPuvy8qf==>?eq@_9Y%H=Ru|YoL)=*Q;TU^C}xdyrS-p zmg#xMV)vF&65jdGS!tu5@sL^F-RG=zw?G7fRqk^44ag5L&z309zla?{coew~C*fW6rn1M!UMR+iqZ7w0k@6 zI&bNhodx1h=0fXdbg!F+?9j_(){t>C8p zE6h1KX&7Q@Uwx>ZXMHv0ps~d%^*UTw)cVn;c z05Im*_wWm`2Y`7BJplf)uA8r0zrX{+8SA+9o%JQW#eE95^>q-paj9|IT4+71=g{Xv zY=be@P^-Vy3vSI1SuOSDFiWA9RmCc26*p&D1%j`vTvovUG5>}63Rlbv|0n-WK}5+S z5Z@rX3D051-|&t562v$-0dru!GWHtV`Parr#s|hqV;RIcc+q&yc-qJ`Cc|u)Va5RX z{_wET-e?6gV(v3)8&!?+MhT-J#61WN2!5mgpZkP|^g}Q=W|#g6ycNF>->;TH z1cVp#x%yLjhW>;;3chLe)qCij^#}D9dLxL0P(!b*m(h#r`QWegbe-SgH~3|Kj{gYr zB)0JNd=>vXQIKi)<&kMO#Z{4McyndiZ~obY#k3Lr5dpl9GOeB2fGTw%;IIzMgGvIw z$1`oT_~OjQQ-vyoo@gt9%Z^lgHkH7`jQCOfvd^?iSfdv%;lm&7#Vug{od<>V3ahZ*__77n?+WO$KAFZDa zl^mqtQz=W}fCVdxCAn$M3xuc8R+4JtLklCv0G5DnsPIRQ!6kmNX9*mvVTos`dR5v4 zju5cKt5^{~G&4>9$N{v`2}#UC1wT)-#5~#r{Y_(OLvIldV!a5LUCHz-;UfY*Dlx4A z)}RCV2+BFJTj*4q{p9I<9SsCFh{ zE0)xvb*i9JgGyeY%@rb0gVbazA4f(y#M&5I0l&*L8YO4iNUAcNK#f-$LfT-$Aj0E_ z#M`7%qtN=3CYxVh()ti&n|_S6M-hqdNvlqvKT>;yw0Z;_kzyJS;4rPLjejjQX00<- z=|rF|t92l)J>en3gM>DO)&%%)BYJg9MB-)AS`h9hG$%A87=$JSi+~U4OlwFQod>Nx zX%2!O`k7XTjQCu~w7W^GO{hhvY16r--A?Snzcw$HC7z=ycRFa4O}io)WlJkhS~-I3 zfn`W5O(;c>{k#NevMCoMttg=gp|Fp?m!GtP1ld)jF&`Q4Amo*Q^>$oOu8@n$auVb< zDLefs@;pRT7LZY%Bae(OB5|Csn{b`V@Nt=HE;RfM1w>7TO4wuwI5?870ii^gYq+KRlBK$&Sl}NitND^KloFkkioFV)~I88W3 zI7#@CaDp~X?JRN3y@7iH!(VWru1r(hiX}k+g3Ja|t^LUs4rn*NOdP z{G70lu$QohPEukf{sLV*5<6*iY7>diWEB{hgB|QkWz?tP>%ZuRi7jO8N!o{`l_qTi zVLjmk!aAx#jXm)`8P`y2rEZZ}MaGqc_XzJ2-XW~;i3@Pg>XNd|r#pIxM(POUa>NpE zQkBJoHwdr$@)q$Du8|9ugQ+Y zt|W;kQJIWO>N}lLhVT7w9t+{}-r&)M&;D!AY8#A7n2dI06xOL8JPj5)ocE*UK3qN; z98OeXMPXTnH4B}@J#0|eE-{?Z-#Re(0M;6SHTwtOB=E1%XuGx1+7g$|s9;2J5vtRX z7=#7QgPUNXzpF{eL8fP-Ixvm)7ApzMoA878ByzAau=H6ST#M?^4-|qrUo$$=+K8|z zY@!MMG8BBsO484O{2pr=PD~VLEzm-LV{@?h@OMpwwVI;6Nw_9ag*8T*o)8;`FG6X3 zwA=@!IT$^R+zYbF>Y}|)R5;O>(Zd0C@!DW%&uWGzp$Y6CY=&`tX)Ew;8 zdWwSGXGO41VO$o9mL;0Ag3$=D7eGsXT;>CN6?O;8d2yKsm${?0i8~l|mYg8_EC<@N z<1!oUNoN+J97Z?b@1o}FgItAq(J?qyFqI$n$H6)lE=?%S!3>mjkWI!>hRM?jS1b(X z20gXGd_^Ky>)#9(xEH)M-^kbSFJKJ$cNo_;^>TV~h~fVbjKL4p`}?2kz4S@?Yx+X{ zS(xSjo_AgUOTP|L-p=U9{UiE!`j-&dzk&axaSz0St7w#hI026vy^Zb=U4Aya&F+M_ z{#(Jl>WFC={}?wRzTBNKOT3-g6XuVn{Cgn!+-P%=Hwm1Q7ntvY6Xs8l`K=`ScX#ur zq38EYFa#hx!*5uZt#fu!JD;7y_UyWLUl>h)%Fb{H+3(m(AZq`9dl$@t|IohZRCUTb zB_O_DD<{(#>kM-SK;*rLo!4PL{d3OKUacU%^P7LtN!UO5*}NuBQ*%@B4FA{P5F9Y_ z>U+Gv9Aa1Y9)hX+ z$^KSjwL8an(rxQ@GBy~$2JOr)#@qHG@KCrPJl59-HLcH_uk0uMQsA{yO#c+Vt$bj= z;P>_yn``yWPHvyM+5MN@Q+{*zdAp4Fm($*Lo%_5!1`D=%xxgoWo)fws2lx7qLmtBU z&L!{+_`>cCaRN`0|VD-gj;#yN0*aTWE|38XG(Ox4~8Fn6u2i;v6>f8`HqA%Q5=}cUo^4&CQsHko6;cb=n^I z&RHvuSHL~!oCtQi8@+Nax2Eb#t!DP9-~q?-W(GOkK6WFwv_Ha~z*l&`1jXE=UQ4SQ zzv4G=J~9vZJDg1Gd-sYSxEJ(?+*9sR51F z-w*L0>$rFEje39cPPdW+nJV4V`b={h_`KZVW;aJ06|Hfu>vFTK{vP~ozv1foZQQ2z*y3cD*ktQEH zXHv}>WL!tu9KtNZ(^NKwGMjZOWHFC_9bcaNgGSJk5HRnAhcHr^kAvI zL>fJAXbVYOKzIScS`p|^(H4=WQ=;dDxZ;YHh~&6qod0n zC*x+qCxl%Dxf454({_;YBx$<|vL?M{&{P-3a#7hHT46My5Fwzl&k6g;cz{6d1$_ST zjrVhX@A|tQo;3 zOrWyEq)B8atv}&ms&a(zJsBS%&`z+ssO$&AQ7Y?3s6$42&C^bhcAUznKWIOZ@igHS zfldyqNZMZr)}1ujG@d2n2r~Xjn*1}{kyf9|E)dQW&JoTc81)DC02%)wT%@x5NxMS$ zm2jDGiSP@86(#Lw!f%A@glmMW2$mvLBHTluL%?M7mL0D;m1Pq45~vBY&ZxIvO!iIr>j_*v ze&&Kf94iTT^dSL`(M@+8J6SR=A)&STExgLzw zYgZV1F*>AeWNcwrO_aSrt3MCMHyN83wifF>6Lt_~)b!&+H#7V!F@UkmaG)rYeN3LQ z$xzmcv5DanQ6_hOd^k^NvYAqof{zfNB}yXr5|b%6%|$Ja={u!J;bIP zMstS?J7-bKa7aa~|V3fTBT7eidycPWcM(uCf z^qXL`7=Ka3|9jb}n~463ev2+g=c7~657D>aA-xwO_H704==UK4z|!dT=%r|WGzWa6 zQ_-YoY&1N2Jn9qmfG7YDK?J{MQG=*XR0HAwlmp-YLQ!6b z0C@#o@aOtZ`5FEb{wRO2-xp#7cJ?3iTlkG2yFd+y4_L-8>gV%w_@1vrhJhR2Wrz~^ zqxZe{wYSgv46+QY_f~n!y*Ipl%N8wc?M2YQcr-5}dQYp)r^47}T`=2d{G zfrY$0;O%dFn)@e24*c2u$vp<%{s-LM?lyOmyB2Z}yyY%(7r4*3Gu)Ir(H-p$fjJL7 z-7aoB_W`%Dd#_s)=0TKoi~Tn<{f{``KsJIM&Q^#ixY~K!SqvEo<~y@tZpM?&c!(_c zxYOI|?sRn8K(50EkeQ&mQ_(5q6n64Lgh9t)_Fs^n;1~N0L>c_f{?guKe+roq*FmJg zrS@y~Li<^Jrk%DY*<)EyJDt0-DI#|HYWe1S6;9u)^>xy*& z+yRe5-hwZ{&GKVwqqW9b0huOWfe3_itm#(LngIC=23dU|gJUO%LwG;rFsK7AW|gec zF!$pQE4$@d95NaF2Jr~bnkUS|kQ;KZxgF+(ePFHxFO=6|HrsRN(`KeQ86p!7GY6QD znh!%>gH{lo@IJG)S=B6WmM{yNxgooO1<^16Fs>RGjnk0f;E=K3*kycTd@4o};woRE3@_w56oIMOZ?3ldzcZ27w+8Xc`xjp9TJhj|(qT&6f!B=1dPs z8a?`GbO)7BT-to9Igjuh;aS2n1iGzi^h6>bXtY^W_B4T>L9`j9Jw=#Km`0dN$RyD1 zm|Q8D{6?fDsf_N?+7#00Nk%?=XcNgucWi9}Y2yiWqW+%1bTdn2i3ciu^ZuGLRSJk zMEoxds<)%{9wf9Sv?0*5nbwN5mV^fgEeQ7$niHB4=!t{^BQUKo85BG8kJR)e&=2=qXsRU@q`p$g$nLS;fFLPbIaLU}?tLRmr? zLTN%NLPgc2)`3<5^fNFBU~q3 zBU~k1A^b|XOt?h&h43@sBH;qzJmDPSEa42{C&FpMDZ)v@kAxG1!Fd~;638R-VU)Y#vI{Od;}Rpqu#PBqz-4<}K7`A5xO@cMMA1$?TSreBva4CoX zQ6?vhswh{%r5yf8`M(+dNBe&a|D%QcLFDj1TIBFQ%5wN0<^LG|M@vCm{>Sh?TK=2i zf3(Ztf0X}Y_#Z8D_#b6C{ExDSOLBsu;eYZBluiIxDxCmOmQDaVMCxU(e_or-pHILn z>8sJj=rp|jABy%zyP{7Z2jKhBJJFKp)#!z2F1-0?KpwzR(cq{ry!UsG9*kN*$w z)?Ya)6BUJQfH~luUk`6VKETW2Ie6p$KKvSD_ z<+%R~FbWQS0QM91D0?t^1$4F_gq(Sez(=5lT^Z){$36qPb&I?RtRG>-`F}OqobXF1 z1QGEg%eFN0PxG4jv-y*G3`VvOn7hGqVH3<7LMUBKhv z0kg4ruk?Q?X683@nm+kNK$gAp!b8IN27C&3fVad3W3}Lt0 zRkSgh8x8)8w?$s?ziayidK&5DDQu@JDzJaw|L=M@)?4CH~LR61`kr0C^Vvb$@rSxEI6- zzZlnt*a#oH8^uVz7{?c*`0jK!DaP&NQTwbhd$*EXnnvlx_`DdK7bEkKyYaB|m9sY- z9u9=BQr*H1VQa`?Ss%VhRSPRX?ukNS9{3_eX0RaKYl0h;^&N)ev43cw_&0ZS;5fKp)kt8Alq9RGj zK>-y}FoB4ONKz3M5fR_)`K{Ib&O7IxaqqbAo_GH^Fvk3Rdvl|wnYZ|Lh_n|7$cofBA1!H;WE>u8SFzy6@1lNK~^coL8 z$i>+~>LC-(4h)kUJfq}CF17`=RrwlkdFmQHY z;OxM_*@1eCz`)sofwKekQhQZaU-az`)sofwKbxX9ot(4h)=301NFq%z}bN@9X~q_m&0cr!afSSNVKnixTJWviO3zPv$1EqkHzym-Dpg2$rxF0AA6afkY_W^}~fhQ{096ATnByut^q#-KLI}iSAi?QW#AHU5%>YP0DKRe2fhQ&0p9{= zfp35_z}LWO;1uu`@FnmC@Hy}qL!e0sG%0~5g&d#;l3W@BO-i6i2{b8zCMD3M1e%mU zlM-lB0!>PwNeMJ5fhHx;qy(CjK$8+^QUXm%ph*cdDS;*>(4+*Klt7abXi@@AN}x#z zG%0~5CD5b6l zGSH+9G${j33SYGw2AY(CCS{;W8E8@lnv{ViWuQqJXi^56lz}E?ph+2MQU;n7G6fn2 znv{ViWuQqR4XUAD1&#oRf%k#;fJ4AR-~g~6*az$d_5izqUBFJ@UEm#H z2e6$VnjlZ4A$qe@^a6SUj{rS@?m#!7E6@e#40HlI0v&+%Ks%r<&<1D?v;tZJEr8}g zGoUHZ1ZWI20v-k$0u6xrKs}%?PzR_D)BOeK1Do_Qe3{(Ot0u_J}RYgezo{UzoT=_t{gM1x13>iPU|7ud%O3yP?PvE3?8HQR1ONA+GF`QwGN+jn z&04Y1LDATzM84R*V5A*!35{&@SA=AbEm1(J)YNG?)_nx zih1#-e#M|!tW?m=J{CMo=`S`0qh&W!_g0y=tPb+LKiXeu7xM}Q9o=naReCFatuw(J zMQfDE zzj@F3P2GFl=5`~yE+z18MeoKBjb93SB`iN{a4i0vd)7bh9F3QvbdyUG)#P@&hFd3o z%gPz23}&%U{oQWG_(9*M`v z9^{W#4W9DP#S-z~++*&`v2Uz@obT+s-Wk8Rlf(Wham%{szU2SrmGkn(KD1_dd)y!8 zo?wx^-#Kp2icg3Sh}ZJk%U2Rv=o^QhVhy~B{&r^+z2~3q51{P!nSGtU**IyHai*Dm z>^Cd3_hK+Kaa7jxHaZQguJolunl;W1ytV#YR`K8oXMlY;(B1uX596?_`}g@rWh=Vc zzZo31MkEf#7X{ayrQR3u-R|MoH0#gUi{^v=G&$c(&|4boQcn7OVyqo!Bki}mr zH~8u9PP>cKJ2BA7wvS_9USzx zd)hb6m#zB}+r7r|w>&`^0ZPU{cP=}NtaJ2+OD5+p`$Bx0dCE!mU$M4%u6Zo>k3WGj z8su|dqpXn+It3|VQ9D`0S?N6#EAP~b-9z~#PAB@ro~Ao>-K|}27TL>OlIR$0aBn(a zdtKc(%x?BF|EGjTscu$#yC=CjrtvORr*)P^j7kfj;=Nomp`jQTrLXgb{iCHXX4x&uu*s~j25sLK=d^^|64 zZ-)-LBTjR_W7{`$&}~hcHtRG_iA#3`si~J{8_Y4=J`>5pjiyaNv?qJV0 z-X`sOScX@iuj9FN=bV0(?sC&oN6m09-M1$X-K!_NoGsnQrn!g1nPWnemA!Odl}gy5 zqk3rQPAyH3XZsA>BwM=oOiND>i=B7Mf1xJS}AH z(3A@e-JGTpp9!I13HlhHmRrsCd{}^<0FYxdTPw8MN!Ekd(vtz2+lOsownx~~{b2GR zWQ(If_wUJ57pqtvu9==-kcaO5lf^NmXBVXDPBZ<=yR1KA`!?HrZ0UY8&0T8E&{!;F z`#8^{8~HSAZMbgivmESrl4lC4BHfv$_0wl}WOIkbur)t(F>EY76rq_b*|UZ1VzzUv zW8nha&TyE}a=zvA8^d{Bv9<$T{zdkmX1kj`m)PD3OJ-tsMRxECv}3IK0BRXw5$pss z-48>@(H3o0=XaPS9y_ZY-h5iM?bXqEV{o>Ka|E3zp*Y5F8dfuKD62oJo6BH_B-KRgY|5- zZ}HqEY-fivbK~$dWzU7sL(eqmH$GvzitQ}6H0m(T&CL2sw%J2RA9gok$1AM+vTn@w z-mplVC2xkt+pY)f!A5fJ=|L9#DE1@w4*j#xqs?GHw+8+7a9t0uV+z~3Tm)76ojv{7 zUS$6{)NE(Cloy(k=xqDAOp(x2nq?lg=R!vpw>mGoDxByZni@QPoGp#IL>|eSqmlj; z>(#ts%J&-9p*h=TVafYhZesgdXeqyCXwh49v3@ozST8h%xLgPJUS|6Zd$^T`BaKm+ zY3zBJEy_R0CC`L2X~Z|OW!Uis+xZ^jm(brPl==^$tj3a?ahN8W+|@#NW3L<+|1q4H z5}J0QDa=kZNgR&Pd70^Kx3cZbWp1&)#P%1q-Pym6?M&Vn9TDWcaLxI-%&%;RahVmY zGlet9vZM!Tw0YTh=EtnLt!wBTUD*Et`(I#-8gLA)#g3oZuJL2xI{Sx)w^irHq3sXn zYJaoka55q+k%yNl%HHqUJ`=hJhsNf4Xs$VV-b%L5g)?10v~9Tvq9gyIXvFS`?8e4c zVcjq+%`4GQv10+-`k{Lg%lFtWW6OULbv@MD9`-C@%dJe~fYX~D&v6l~aWH#+WX~10 zp9btI82(6cmfhIaW7{sY+7|(L$_nfn!!|uEUn(@Mc)EBfhlgfJ5L#~VQ$vL#xt>dL zGt@J4iBaLq9-%?)U*Zzn2DGnuqr&t&^y`blGQD{22(}!Sa_~xc-!x$F1hyOxG!6~z z`4|g!4`hoYF`qT}3%x|R?rgCVw{={}Sd*-k{%X%K$9`Ogc&*|}SO?B9Jlx?ff(Uj$0w!G&bWyvi}OAocS znLWcCl}o9%_x7 zhn5UI2;|$?{W%vb&w3x*r`Yaf|GR7_vu6|A*TNN`D}T+7cen_48GiLe_TLT*HV;ju zL{s{PzF^`pd_AujVa@}JLJPUvR<_TF1v^raKy(TV3hc)3xghpF|D4_=F_Tu4B`p4M zXt)6uNUWe!?Ji+qV-+v5mhF>aiR_8pS{8AJY#{28wZhZ^^z+}a-9jG-So&?&rP%Vi zThhz@`Hd4~vl!#a2F4JwmXSRyVqm8Y2(@-GeEl}0gVy3T#ipHaf{h?tWlziAe*7RIl~uR2$q_v066 zq=K*Hp7;rQBmPnR{rEom#(%kc(VY>0H@?N}MsFUhGdsjr$Ct@#?x6T$Cy&!4zQ9bn zh3LKR3-(&s)9wqfJJ>UA%c*HC zu@A*tIVY@-ohtDr@%r(a?r-Mmcon&no(Y$f8{^6-E zw?K}h_sh#W`(ig@*W6vP%TE2+`Pdn!sC_y1g`LHc_R82Nu@CIqu|u&vPAjWeY==EQ zwwW^M^>#fP>2PgqWo)U_Ew(5&pPnYqq_Gai$41f^M-82%J1aIM){mYmcafLf9JiH&!E7$r){~i&+_Y*3!-GL~F7qnA1JJ`9V2uqrnVYPif;Q&j zpqV+BvMAOLs>!uMg`l+gZcr>J6y$X)1UalivP+Q3rnJj}7f7?Df5*=6Y>`v#O8!k6 zVezVcjoueHYrW}zW!7}=$lLx2|0A=$b&*C~+(&N@Z1FcaAITl|NPnGuhDNj`|3S)1-n>(4tUT8&G80$O3%--%28jZ1vd&E6p zt+(dUcNrz+W~-WC)Gy%Y@w1ux{J2?E9y0IHR~m-h&b&!)3yh^~3%9)+)*!Q$cg)K0y+E zL`2KbL-{$wlE1O$lS9F$GRBkazlZ&(X=c_q;t%lLDQsV0i+z&8Wj3;BGwb`NnVr-(A4|QEXAu3q03m`w07Yv#rXO_oPvo=W;6*@3UUYbGNYN zIvPIv;o+7bu%Bmh8La;kF0+9xS|C~x+BUam<4-QXoy+uQjZ?FK*z*y4_Y#XxYW43&jZM0^8clOL>%WD>Iu`bKD72EQm75B1j!=5W__p=AvhQ3yt{lnPi z=D9=IR%81*+l!$!9%k#X)A7%?KHO8*}fE7qhx6HzgY905er!_V0)S8-U_Ywf$jHf&$IoO?HRUT zvpvQ3E4H7r{fzB#wtOtaG1ecjJ;L@d+XHN|htVFmg@`@u-^F$(TW&w%9oE~}Ze@#J z$?Z^})~~UjkG@#TdJS8|&(*9~v0ct~8QT}wE@t~2+o##iXFHeeY_=SK#4OesY-h4f zV>^}Y6t)xCj%PcL?O3*>*>W5ZBUuk;JCrT=7cq$SK(^dML_gM#vhB;Z58Iw>xjl%k zth=!7%(fHT_H5g+<<>4*ux`$_DcdG&8?&v?wl3Q`Y-_Tu&bBJsDr_sWt;n_lTlAxH ztjn@3&6Zo8D9*Yl+ahcWu`S3pKij-)^RUg$HW%BRY;&;9!Zs7z1lu@UL<66-WNWb1 z*=nIR?y$Yh_9oju*xq3KE8FXAe_?x#?ayp~WP6D%_YQ-5hjE@g-?BZ+mV2dfhV^N- z+(&S&WpE!cPO|?5Tka#qN31_&%l*YT%9?wPahUacY!9(L$o2r+J#2Te<(^{fV7-m) zRlfKBWxIs!^K74EyNK;Vw%m7&`K+H}JBMvL z+nH>+zZlb5Ph~rq?IgAn*p6pAn(Yv_1KDz4Fdk*iy};w`wk_Fmk1(3D=DuJwX5El&1Ge?q)?>@P!>Gf$Hrt2T)?izWZ6&tcJB$ZebH4~P z4N^NS&X)T^m`jj6_pvR=wgB6E+2&`Pmu)V#IoM`r%Y7kC-AHRlu;spB1gu@Qf-Uz2 zLkqQjhwa~NZ?nD0_Kz4{|5p`)JE%8y}0DWVyDX~TzN-Rw*p!)?=6QiB7;$~t{ zqGzImTS3l~{SwU_Cs9`hi7IqAps;;8kw^R_yE*ULlkKx~SHO~OXvD(n^u56ur&Ig{ z9CU%Z<$-EI@3tUhJ1s}?IOPdL3}MPqsC3frM~x_^@uoI}B7H-}p@I7`>m z9|Zg8`gvopny#7W(?|r9DeHcLV5C!xMp5h$w4=NNwS!9ZmVBWgm-T)?8|B~f>5A9M zV&Cyk`=7`X_G-G)-Q{nl>)Vq4a>^=S)9&Ff^5;5h?RNeQe>`0$52L|C_Q^AT7w5b* z=*qSsjV@4L?(vKH`RRRm&uQUn-VMqwaL)U}4V6!>(beT`$`DXgHgd1i z-Ip`Y$M!bLHgLrL&D}%ad8~I=x{E2(z)W|dJKTBG?dNv08+j$X!sfePeycWR$fIji zYF_{S@89f!u#XD<%OEz%Fn=e6>Jq8Wk?I(!=-1NQ zN4{upq~EPun`mydrRl9AU&~0fh*a}PHH%c!NHvL6<484%RCLVKqjr$qAevi0Qqi7F zuN(R5M5=bAYDKDMq@uQ#UL*2Vk5tr#(yKN4)GFWQ2PC|+gUtz{IiGNO2u5k=OFC|+em`#z&X z^lMR+%824nMihrK?jG}~z8O)Z$%rCNMifCZ>P2ggB1lFQK{BEUl5uyBM-e0=iXa(x zdq5OHGNNda5k-rPC^BS3ks%|B2N`9eZ7CV4yR9zj(;3C1xlvEaC>r^SMCxvzj@oHP z!D#N?-gs~1%O9z`Z8uls%NeQck&1e4M$~6AGDUNPNO_Tp`gDd9`J%p%AtRrNl$KJ- zX_1;5si^NKCr7@Ck-FR3$49<#ks2GRF_F3(BSuBOk&zl6siBb?5~;zF8WgF4k&1d$ zGU`#usJ|tnzLkvnRx;{a$*3PCqdt?2B4IM>J;|u|B%>IcjG{_1>OIMJ(N;wfBpLOi zWE2mQQAAHhaWWb8sASZmk`1HvMSUw7^{8ajqmogNN=E%C8TFZD^j9}oHCkWPkCIV8 zN>+^KMm;K7KJrC9Dp@x2-Hq8%-%3XPC>iyhWYl|-g`#DnK9h`kN;2vx$$U|{C{ic$ zM84dSiuzG9>PN}De~F_WmCODFqkfb$qPcpcq8^nV^`rEt z_oPQXB|YjX=}}Kfk9tLV)GN|Qq}H4s^^f%EZ%=yEQ_`cJk{L2M* zuSk!2MS5@bYvJ?%(z1t^xIyEeT{kb9XUvl{_Ss%@tGR)`I)C1LipD&fWR5WVn>}c} zvnFP3vogIOeV>`zOqeEpXa0-)L7t&;?vKblG$!AAxl%5s>-L#6y4grMAiUa7d_h~!%MuF{vlG)PgW%9aAIcusGSQH72$ZMi{P`2v5+0?byb-?|KNtUkp5-5i z@1UpmYvN1e3*xilQ{$uQJM5nE4)JF3y7UcpnRpS(379!<$L_>_i(QVLjeSOA3GRz+ zqo?JoV@v2cd6K@l9vK@DdxSFoHKnKFRbr)Mh3Q-COff6C9sC+x3cd+W21kRvd^P@Z z@Ivr3eYHL%7!?c(deRr`%_zTLwV-TJG{{T2{T%-eeW`xgKTBi#eL!ESZ>OAoYy743 zh59UiDt$>X$nQzdkem5+{VIMLzleX2pV_y)zbS{`CGQ*hBK@ehm&Wbe;H~nO(DU64 zZwh^hKJe~Ub$B&R?_%RR`nr3`{l-1%9(DJ+TWM^*RqpfdQzR;GA|oaSl7Xoh{A=XBB1Sd&)_tOb8>K{!Vww#n;5C?Np-i z_6j+Iq|{b@pi*0m50!dbbXKd|JX*QlGKMMjrg&Y=e4~bPZ4xb% zdfjNP)JEYcwZS;3)Ovk^Qm^T&m0D-KqSUL}lWN6lYb)0)BEM2=^zBN$tfwor+K@`E z(%w^QrC6iX3gdaDmg}>XddbMG)H1_V>P2xmj&RN|>2YTi6UZLsmAD4=|EjWJ5i(VkOkwsBag zCqyNsW*ILll_97 zfpJZ#r}ed1otWQAxteK`(dE7r_?kfuToR>X=)`?)IJxJjpAzNB#~;a z6OF~nH$l&;)Oc;JQseY%Y9(XUE*E2rs%qwFJ=I@F=@*o5q&`on5u%t{ba*%Add%3b z)G)D8siE5UN)0hmRUE9ZQHu_$tY!|>Rf7-%^wdiF8;jK3ej2^i#S!jNT{ULWSD&wZ zeYB%W^)}v7s+W-pm_79Y%J+!zjZ!_tVWqkoW0mTrms6^%A(ZN(KdDq_y{FpbPWLKT zN0CRV4&nu++UqYV)lN^syuYFR@ptTVk_Pe;TT(8aK6r z%J+wMNvRw9S4#aZK2z#9@qkjlYEP@5y*^5bU)1&**L2l;jGy(GD*uzXq>BAmQ@O4h zrIfm2r2d{=HUj0lq+e0$qL`}|{h_^bT`)E)*Y~Q08|U?@YUX!hqEhFK=al+ZSMAR@ ztKY8{{YG^_ID_vLWOG7mx;|rkzkMX$*iUy^mSH(^m zuKKMLwsL)HR8;B{L-ir!xM-?;9~)^(9n&9G>Lb0nQXgvDl=?tdfzmjtr?&WrzD3PF ztiP?)`^Jk(y{A8>)FJ%?r4DL))#e>|P`UOSsh{0vELFa}hU&4#9&Lv5?KaM+MR&DU zuAL&aKi@T0D&ISLL8W$RslB+}P_5b6rms_Tx9a~WwZ%B8)Z3z)Qk#XV)LZ&-rQS4B z$LtN`b>-Wn*H`LweXdd)jnztRpwGEDXc+5_{7Sv1rIuZ%_f@`Eja2hlE7ae2;}y|M z&0Ql@h&Emp>B_fSEKxgSRW{{XDTXSwLQfs= zl>Ps5;+w?D#1YE+zd7+LU#UMqPvl1>1|+&C+E9-F8i{g=qVzU?=7bghD}F8hJ!SYi zMpx-;DUU_>g!nx<+pruM@8vFG*iD=ZpuGA?*g`NBfqp(2vIUP@evE zlo#z;`u6+r*f@H3y;rPbtXZrMWkY))Rv?x=<^})I+vt~qGr_09;b2$rR^q;Ig|5kOQ=YgB-YLqde~_MczwW(ESL9E5GrdXPa2n683w>AKfJSmF z>lLBv@q{PcKi!|*^OOtjBX>XL&EM#*rmJy0$$pIT<#%>lxb-MYVJW&6&*=uP?%bg4 zglC$c6?j0o25(qbDBJx>>xi|R-q?86T1Hv! zXIWFMk#zOloiYnPOz&%yv+k$w88Taz`4{CA{GPH2erz73w>4g;Z1yjh^XZPxL>j~X zQL~HL(yVV*r8hP1Gjo|SdiVbic~yQ(W7Z#~yE$*mb(AgeSvgxyqmk+d%bv15UB}mw z6=ZRlpYj8`;tpk~zerc{pHN=Fcf}iGjd(%K7wL2j|Cs12I*S&fzNjY3h{7U|NYEAh zpT8XuE1SZd#q)LH5ulGNb+)^KJ{ zgFHZTD&&hKr=aX*lGG?xF`7LRvptd{SnBVS9L`evndD=T2T2ZNDc&MEl%-LYHn+}-$5y|$Dqe!-6sV%2%Y0KL(j+||1q<~c zS(&ANg_f$sOZ6mYMa=({WChHBg5-mc;c+ex`3%W&kV%qdAuH3m%J8}-kh3&9jS3`7 zL0%sg~KfmUYRQ4MoF1krhRYk<5akZ&T6CTy!)!GqF=FB$>cc z3rNPX)H_r(#zpTVXMp*aN&1+7i=+qnG|BMqId!bCRjPA=Fb>%8hE2^v*|31gQY<5> z;_YgZh_|9ENyJ;d7fBUwvyw!-6>-B!L%gL3?xZ2!8cj*6czcJWinkOr(hzU;zJ{Gf z@s`4A7ILa^dyOQ*tAHEd{*4aJ6kDhhSY4AJSC7y+l$4+;t=oaP|Hq z5peZRBoT16qa+b<1qH4&1YFUMBm%C!oFoFS-hw0ou6CFwtJ^f6;w_b|PKv@Usa!N0(U$k8ince%i)d@4k)&uFHi}Xt5pT8Wv;vB^w1V>F zM7T95oTee%8bwG_xD9u521&$QN)qj)A>JBqkwm=J=Fz$+-qO0dk)m)*D%>rIwxS%# zX4rs3BvrIckVLfAJ~ON|inea}_aF_iR#4DPQ?ZuXWEx_vppWd*DAv;Y`;il&R_r2) zP%9{CrXkddq9j$Q9Ym5sZCIB_Ng~$jeMlnK>V-)n){5LDDb|K9DicWxwxN_H5p8wq zuW2gUt|5tNt5b(hL$oz+lcZ=%iy2`%QsMS6NfmDIBZ+V;!kwVPZ730LwRcG(+!{4V zQn(E_{cVznw>rhLG{jroCs~pUXiG^V-iolA6mP?A2%9wmt`QzR1YALpA`JmogcU-- z6@y8tfZLcP0C~aGRZe zm%=UW(mv!wv=v{Iq-aa)(qAD-;Wk{YMiTK>r>;8#@m9Z=RzUHVRzQEOXCT}f;kqc? zhD(Ke9|70MOZop@eY|L@QC9NSG&)|LSXCMuuQ=r-&rNTyd$Hq`&3tccd+ZGwS#L$` zdCFz}1YNaHh>eI1jP;_?{Pxn-{~MIse+7-?_jLRT8pm$}OE#w(>DdZb?~daBEOTT<*^UZbN4ND&A-N8Mx)-%vy=8zdn{e|_qQLRG4ER158Jiu%61w1e!BpTc$bOB z8qlnp)-~%Qk9T)mbQN<%TEZ2R#9M(b>&fc!K^jBuKG{`{qLJqZ(tVKU{ippW{KuUf z{sezSq9EN5>FsRsyZCKHQ~KJyuCvjv=9jmB@=N%IoMrTNdlo0@d%i(q4_@~!TYKsI zfUoQe^!54?8hvnw_og$?Tj#AbZ_yo-1=a*_4vj!KiQZ!vY_<0KcwN1=Gy-8gb02++ zUcoC#?=$30=w3E2NaXfR_ixIo|D*f8)4~0k-fZ~LJxJp}yiM;myzDe|U!+k8pQQI2 zrnqC=q3)yfhC_R|xwFV^;66leIh1yb(ins}+_=BjwVXTl4Z6ef6Wvd_;GA*ti?(90 z=p(aA-?5~i_X=L1dlFaZeS+`ADd!`4zu>4dOzf9w@k{2L<~sikbEUb2zARke^ay^S z_dh-ljs@@0*acgI*Xb_Pa;J2#n7+xF6+9kHkoVBtrh!4PpmWe#JQFky>d2-+)u3EZ zJSa$`J!TGEb>l*|)g1jv?^jzGhThcsnmN?S!>r?Kn05+)=0s*-yv3|1x-jdSPl5V2 z%$M*#=KPImO3WFqf4FmtS*HOaX&#kXu=JHI?$_DD#{+!fc!*V%k=t!n0 z!OY=cJ9CKNF>Q2bX7`qXvNEW*VP^A=fEK;>LhH*W?qg=P&x0n)XQgC;;ruLKEzqPC z%7(6Iad|ScGZ(blf!b1LCg(6H`+(Y2X2OGitWlDu$H)&r@hEe&H3ignF!PwBnO(fk znPn++X1F;IO04feJrhrtx3OFW@gnBiXPNE2eaw3V4r>JuhwmN}yP=XB=lPY)p z19-9`-LwpUrlM(q`W$8jIg8oJ+rZ4LsnvP!@N`4(Cj90dW(V&H=DqeLP;bV}@7xPo z-9Rytna{)C&u9CX);BU+d;OUAy7=8XGRB)*2aUG2vx{ffHc|W9MsJ?3rPgZl$#jBZ1 zF|A8xRR@)=YURhY{u{H3dk;uiX~UgfMO0u`cCh-&bjLXKS2oHr%W3DB1uV3o0>%qG z-Nq}$Z0^B7$a{dN3kr0xVO}vz+e4X+JRF1}9)7=&i-S|hdV}W|G7$0#Qhwm@_j`L9 z^FDnu^AQ)J>ON;H{3dqeNEdCdoUFtC!R{x_mhOILJ9i7SpNoUr!d=7Dg>@XoX6{s; zZsOv13sXLo@b?Rw_`MM>_CpZ^zca+PdHxV%5Oc7(2NVdIgXBb>9;EMO4m3TGGAxGc z9pL-|+LV=>4kMkZhwlC~fDqHfQSYx`;^}_$Cp`4`GmbGIbh`>0o#DlxWO7_3J$U5%UBn z$}oF4r$Li)a#OW>bmd9Pa|mh(l-;a1dAggx{I1T=Jl)mm$BT6x$dg^@v?bh{E+RLx zGkuI0PIs0kK@l)JS+_y`BD14&0JJIFIBiA8UOd^stOn}na_wnE;Ba;AjaJNdW-(C1 zPHgLDF{U6|SeX-jLbKc?_iIEup`aKwE8T;b62hTC%@| zRSDF;V>YKVw6J`0<1(|EiNMs%KqF{sV!N6e>v?_?a|&0lNgkeTOoJMSi#OJrGaH%x zKpjo-VF%TH*p9RRVdFPu5&G;kTyC_7CRtR&-W}*&VE_FByQip?kEhFf--6<6W;q83 z_kQ;UlsE5Z*7I(KpAYK0oH9JU%X}F$&?R=#kjvrk?leASzH5CCinE#~c2iPdY|lGJ zoTgvbcbHgihyD|n+wMFKS~WrAA1arjZcf1Rf{%jKZai20U-p7NG89Mk$Ew7Sgto7Q}uep8=7)4KkK z6&|F}rQJ4Zpa(2)jqbFAGm`R|f;OZYthPmf!__NVy_ zrp3XKx^AJF>DoIyt@+rWnndT)1UFypAFm!S_m9Oj)&3D!&mA{A`|n7uiFU`}=BoYe zZ({%7l53>>EwJ6Uo$mZQKBWQ=f8V!JV_)CE)1Hgw>(Qk}INvjxFO3Da@s zff6ys(N6KS?QH}lVxg^{;c3g=4$>pcaQ&9?Fw=Aoff7-`)DH8sq%_xIIVsWYq*%(+ z!mSJ1T|uKW)9`fA90eNNm^yvP9Ihwkqx0&S3uF~Q| zW+|sJC@JATHIGtQeMzS*yGvU2L476j0k0)!od5;GKnV}Kr-Y54Euqci`NbU^?&3D0 zR&i}L`-?efZpBO-rea3c@cn<^*`v7{W$BN**%N=ddH;1JK(|(EEI@aOI~U^ty3Oy7 z2uM%9)9;Q7=uZ9L9T)IlM_Y5d{`>LP-23j1xaMxbm}_pie?RJ)dm#3-`^W81kq|MSu9+!cOtzYu*#pvJdz+x^EQ z+_@$G$79^N<{uSQU@ed{DTlbk)=-$qB(Kj)ka#s#W^puln%*$3@nM9*RZ8Q{PdGS(ndMYwe}*@gne@6b)4fF?y=4+^*G%VkW}1ubo}ofu z%zd2w^PEo1C*94=x!zdj9Je}i4CUYm*E8S8?>*(UkRQ;Ekhr=UdD=rPihC8g+(K_S z^BEU^3K!653*qt!uRilRb#<}GU%=DP`Y$pwx%?+kZ{R$~(>dHfna?};6SUaZcsjc~ z%$%gzdUlg1m)Q6Vkj=%JR5tr{_|<)stm;ZJt9_LHFZzp_OWh^R7aW{|WVWyH^c(hZ z<|gk4=IbtgXQT5ZPj7I4W3Km~V!oy>e%ATe{8ybn*uU1<&wRz1VV_{q91hX@AZ<ErC@9v^2_h5e=MZ|BcuR`=0mM*7%?1AOee(S8e_ zpT)1u9P1-gwD(K%bYCCmx0U@&Jbj-ZV~+5!pK{aiHsSuL?V-&T^3X<0c;{^UoEFG( zWGz{M?oXbV`N*D;r5(xzEFW;P(A;9-Ai(k-wz-^uO6Fu;+^M1Em$}L2l3Cf4nRFm2 z*ASHt*p_6Qjcv?nN9!!hx(I1YQbr>xSHQL%`OkDx40zWZ?8ZYY*rqmc*CW`zH z=Zl#72U8e$OsEmE(&1Eti_OCnMpR1;aA#rhzc3FYhpFM`#G7yq1D*ubU|$&DNvOfa zE@Ivfzy;uY;5_gha1QtuI179OoB_TDP6MZauK=Uz*b-j@HVg+cnf#~*aTqgF0m0)8-Vq|YXHXX5*WKnVC*h|vAe_?6nPoI*j-{3 zrd9yU0SwwDmSJis@B**|cpg{`JO?ZSo&^>H&j1Smy1)*P%6#A{U@kBRm<>Du%mOli zBrp?517-k^1Ji+Nz*JxgFd3KxOavwXrZ)B$P(wSbyH4WK$u4X6rK0V)HPfQmo`pgd3xC<~MUN&}^UlE4E% z37|Mo47eXC3KRhf1NQ-ifPz2);9ejv_4|sqJIDidEKmZ1y0~+H$9-j11%)1Hv0o(w71AYar1HS;*fS-XM!|VTfW_~U4 z=f7U{r>^;Ny?^&=|L&Fk|MqqMfAN^=^ksen`U;@3U-sW#RsYA=)c@lv>i^m6Y4-p< z-`+xZ1J=4L=+4PA?p%7lJ=GmYqZ15td%IoTc5ZXGA$`SP#VzNSaPM>Ty4mUei|uO8 zpU!pqg5bRKwey*C%z5A0OQR^h<*avJc9uEMIrHgTf*JHx|7d3@y|wU&)5&Q~U-s8? zYB&|?U55Le0#0rxi$h}<(&LF6_Rsc3`&;_D;8Xhp`yjn-u+`pVziO|vU$7U_m;On6 z8r}CEVGp9)blvRsb_;srptfDrE^j|z7q;`+IqU?zbD&$dtY59G^xgj%>vMXPb=cZx zy+hv~Y@l%=Ua}U`z1i9H{r_ZZ4CRdMZ}p`2F4|BIfcn-$Rwb(pWsWRJc>uCn0gVlK z$NYo7N4P}!BTtzp%n!{&=5BKvjc2%yGEFQspEc*18Rm3z0=@k)*z9X|r(BXP%|>P& zvl`tsC}|ck^9SetSMJUH+dDJ=*ULj!b-nnUiu@y7YC#U-CEkqr4!$kzdH; zl<{J}e3!nG*eGA2?lYOB-RP>3uJB8!8_;vlj(0ry`A z0meaqaS&i==#21hxq|@XSeP;agc}Fp#zDAo5N;fV8~YzFY2N|<25tj?0k;6eAR95r zMhvnMgY4f?1fj@AD6$cXY=j~kp~yxkvVXz?KLS^QE5K#o5^xdt0k{Bs51a?S1I_{8 z0%w75fHT0?z-izV@D=bS@FK7jcmY@fJP#}eo&y#E&jJg9XMhF3)4+V-DPSJ(Brq44 z1Iz}V0A>LhKoUp?W&&xz4B&BKIxr2G3QPee1CxM>zyx4CFb)_Ci~&XiqkxgX2w*ty z7%&VN3Jd`T1A~BpzyP2>&<}VN=nM1#dIPnO51>2H4d@DV0XhSnfQ~>1pgqtI zXbZFfS_7?smOu-jInWGf3N!&41C4-(frdZ>pgvF!s0-8qY6G=^n!rOq4WK$u4X6rK z0V)HPfQmo`;6b1~P!1>ylmSWurGS#a13(F&I8Y3@A1DeG0SW{60fm5qKmp)hAU}`~ z$P3&9)0Wt%bfCLZ+Vn6`+fCspM1K5BCn1BQXU;sLxF|2=p zJHX$-ZQw897Vsx<6ZiwT0sId92K)+K2Yvyr0Y3vj0Y3s)fh)je;1X~V_yM>8d=H!l zz5~ty-vVcWZ-6tv*T8Au6z~=BCGZ9CIq(^95;y^T3VZ?_2R;Ul0UrS$0v`ZJfg`|S z;C zf$iK5Yy-9eTY$HL&A?m0o4^~uCg62oBd`Hj54;Af16~Ez0>EL6opRp@k>=b!HsEUQE(AhK3LscwP#X?mqRE1t!<|3#Hoow@zs>+L)S_-@XECHSe76Z=#i-2c= zg}^hw0^n(2KJXMU4|o!o3(Ns#15W_6fD9lBqysa7G={DE`wTcA2c`qlfT_R~U@|ZX zmrZ3fb&j>69Dns=4^b9@GO=Rx z{X(8tRvO>Vr2PAT1lNK~|9w6Ff8u)lzxvAf|Bvhc|MELyBj~|a80aMep+q2*2!s-W zP$CdY1VV{GC=m!H0-;18ln8_pflwk4N(4fQKqwIiB?6&DAe0D%5`j=65K06>i9je3 z2qgj`jZny2jX)?72qglcL?Dz1gc5;JA`nUhLWw{q5eOv$p+q2*2!s-WP$CdY1VV{G zC=m!H0-;18ln8_pflwk4N(4fQKqwIiWm9Z?6QD8B2zVH12s8ld1NDHqKpmhqPz$ID zJOtDLssq)4sz4Q>GEfPq2vh(b1j+;DfU-atpfpelC<#0OlmLnY#en;PqCgR#FmNAG z2q*{?0PY3y1Nnfwz&$`7AUBW;$O+^CvIE(GtUwkZGmr^L0C6A&1b`2CfD1T)4OoB) zNI(DvpabZC;vb$8cYwcv+rVGIE#Ob!Ch!Mv1Na^I4fqwf4*UXK1AYd60)7Op0#|^` zz$M@!@B?rG_#QY9d+c0!M(u!27^^z#-ruZ~)j3>;v`!dw|`*E?_6{F7OVp1K1911GWNNfVY9o zz+1qZz#G6O;B{alumM;PyaucTUIo?yuK;U+mx0y5DqtnB0$2{b1S|ty1o{GffZjka zpeOJM&;#fWbOX8qU4YI&C!izH0ca1j1KI*@fYv}Ope4`(Xbv<3ngUIL#y}(BVW1(< z0H_bt1L^{GfZ9MUpeFDTPy?tAR0FC4Re;JsC7>cu0eBE750nGS0%d^GKq-bqlagps z5=}~?Nl7#*8dRA7V4_J$G%1NDCDEiLnpF6faCkz1CMD6NB$|{&lagps5=}~?Nl7#* zi6$k{q$HY@M3a(eQW8x{qDe_KDTyW}m*Ur70G0sH1B-#@fJMNwz(U{|U;*$nFdujd zm$xI1P%cEfqlSUU=Oex*ahqa-UZ$P zc6i1q{bA#M%}UVo*}u$R%pYh3fD`5sbC0>jTu)z1FQ%umGdW*eKYAkD)@*FnGAq*8 z_yy_OFJ?-4OJ1W90KTR#@ej-0^fdG}xk8NqFj3|Fe^KT9ADbAG=uK}?w@B1aR7;di z+@Hvo$eM8D|HOZfU-_4u|8K-!iN6?M7@r-VPWc3f(tU!?@s{xh@#@GVAe+Q<#S?KW z_BZAIzY;qa`;zVtycgR=c?H)?H@1p$|34j@6`Mw*^9_yljdi6v1C3*~Wt~`M%Ku-4 zM&-*I^NjandT>)53w{nR1YZXyXcT~b!S-NN@Jg^Ocs7_Dqy-b{>+b=wO7KX~LEH$M z2lXlAV0rrXyFieW#^f{Q2>-VKD~$*6E#)0NmiWUzr`Cju^(l__>{R|otV2nS+ z??d0(xAq(PwfstcDZeni&7Z}0Y3#i}yq~=9z0=;O^kw~CZ<{zoc?j2dFM12#cK&vLbuKyIa8|;-&Q@{tUq=W?{OF7jU&tb| ze>hTr>@LnxmcqtPEvI7QOXmUE)+y-ZbYk@B*q`)1!T0tldKLOT@sGXJev95Ec*%a& zo|E{_e%u~M-;MS$j@z9nb3p^Ux?PSwF3M+TwOxAL@jGQL{FdGx_|V!;645=NrTOFdlb{kJ9_1}XyNVO+c{ogW(gVcVNoz$_rZX=Ve)PAv$1XpU;Br@Jg?Pt4* zD)*C>YJ5MM-znc!aYv~u7E;qn?XpC|V5wa)Q_KEfp)pA9f{6wqwePJ|OFVB*P;2?l zN-c5D>Z5$$T1ZYUwX~gBy*Osr;X<1vTQ?h|lU&)`8`qD<0ajAV_ z4O70)Wh%aWX8o>wC*{3Lov=zM^{Kp|)F)ZX~WJgw$_Xuhx1QLDF7 zN35<&9X3_u(%v`oD&Kq7M@k(spH%9Anc4^Y<(JC0PpYV*?KP1=T55Z&-_+dQ)?}r2 znW<>7Q)X4ZcV#oB-Z7t7YP(1+yVWYFd|Ry4R=sUm%C}h}rLolBkPDS>lda;ew%$%1 znRW7tn)|9mdS|Jvweu+7D`rEb*2n@%y=);#xztu!7nN_Nm1>5QqP#F zJ-NUtt9(z(`by0=7c2FYm5Q44mv#pnvdP25VYL*QfYQ3rDj;EZGPN*RQaY`I}oj>S-l{p*bhKVHp8Us^cQ!M6^@WW zVxqMWVuCyhF<#t;7$*xujFn9x##m(_M#*syBgL-}Bg|JIhKuVEkI82thFNtXhMHkE zdHT`8R$GWc)=-FnvI)chOZ`dfPp36pxStu0$3jazDhEUKl`|mv$gvQ;?HEKaxdEc5 zxf9|M`7J~b84hAX>*;P5g6JmKLv%IYh3F!4L3EZCAv#(6AUaA__YP8d+M9o4Ry(;C zqODmFqK(-RqP0~OqLuYNL`zG}YGFPIPjgePxv5n9u!-3kvl`pkAsX2aLp*H#1kuo( z15sa&`G43u^Z1>r?(bjcoW0N4d!K8NBqT#3nUYjWAyblsBuSabN1S6 zK~xfxAS%jf5D$r8AS%dq5aojuL^N<$nQjr;PHY)5;}9KZrZTs}Lo`Nr>Vi4@5D^J1iZhqzr9 zf+#HaKos)(Kok@{L;+C_;ub&3>P{b;U;YG<&*w`tuQ&lq9$5<_x6c>JO@b}C#Cj~s z>GSQ7L+*g(M!6dzyI%_82EQIeHjmHq^)jk(K%bsfJOgo^d>kU>e*%#VxXTII7?yaz z=P>l6)bX^r5qyzo!5^Vy2`maklsBH%@qIo&QtX69_!}WSv6oUBYFcMCQ4!%`Jnyty z(|QTd`^JG6i^_4GW!u+IaaJcC{9W_B6K-R+9e1Cj-BT($@7NP;I_ko?d)^VZATK-& z-{g6RoLaovK`*)sqAd@!V$=TEH>~z~_)*|_dtLnT^SnJy62p%d&->iNA4bpH?4(c0XHEfL zZqEdf4EP|B1i=_BZyuMs>E#s7B_4PyT>%bi&BkMrD#DSyp$m%2Tb zKb4be0%)-ybK~z(6@gLlVe$U)9`TOxwp2-=k!VGA{%Xdn#>>U; z5rg9;sMcSecy@Ze_o>?7WvVLhO?V{S6K)GPhAXMEzz5-saB?_Cbf72vfmC0hbJ#v? zNp=6~hqdS#zXDbMD@j!b@`X9VETN*O{43^yIc<)by;N&plUZdJoB3v@nQX>T1;Bx( zm)LJQ({p}H)6_g|YMV#QL*_nH$`moTQ00NF#u!&$)fe>{eO&L?JM?BTTd&be#SXnd z&(>3DR>258SohIg#0cF%x1w1E4Rjq{U02li>r%QX%_+D^U#~;$(Tx5dX-2^bbwFHH zJE<`P5>P<>v+LfvkyriB}&(M?qqcoeKth!4TRRz>dDw~RnW;C1N zx8PE6mXed74L1H>g2L<{qHBfyHPR zLGBx18trHJ1N@$TC%+xdAZYAAlooK$<_$ znW|O3D2~gf@@ZL{-d8P|Tr#UP6vuz{zaBU8KM`?2H*pmIZ+^7z=nUSK z8oVntcvouhuGHXNslmHagLkC{?@B3uV>D=B@UGP0U8%vlQiFG;2JcD@-jy1>D>Zmm zYVfYq;9aS~yHbO9r3UXx4c?U+yel<$S8DLC)aL zq2)V&PHRW@gH_=;Yd;-kT6PcnSS|~@S@sUQTAmCsjs10=Vtp#al=0V{tXnJL9d|YdAjVQd@tvV+2%Qo3F+s2e9XBR^0S;($O&V8BY zch+fZ`JH|Y6qxw^oqMlspV28$3b&2vF&d~ZZL+4v6umfNkb2-qFwL5eV0Qdreavok zSbhe2$gFTEm}*x$bdxn7=pRV_IiEodV6RdR&sGeCS>iNqc5&%{rZ-yqXW|CX!RKw#*v}^MuC;G+aZ7FV zd)W3y54k5csM)r?!EXdQ$V|1~JZ;Wk&m(5CzE7;Z7)Go8$FmFc=Ja6D`M?wxBo%#!gs{H8nuL1?bhb zd?%P-cl!<;!5CB8n#cHGfiBL%=+Lw6(f$LVSJ!eBC#xAHa-!{hX!$nhV|rVRLfd`F z@+|`o@Rq1(KlZHx);!YW0>xO+EoC{v;F297amkJF@bSYrZPIYRtX*%oYb-PU`@tA4 z;S7Uc7#XspwP!emEQcu^&ahw*+TMGX>3r2gWnFuCLvbY!F}Q+ymL#YrBhtJ?NJzaQw%wH#nNfc{a?*=gC|Tm)qTbnmq6 zr||jx{Ay@>&s+92%RzrS=(M(c!*l~ZT-tBAlWn^XXGiKIzed~hEqlu+Ko4iJmzrhU zz2p%vhW+$Z=uc098@#8t!!GZka1r&8IKCbp&U$x&Yqz`mxm~ZD!RK}hls&E8N?CJP za{%=31)YwTUDS1;xBz+sEnk=GKo4I>XN9k$GbgL+?BeU_q;I$Db&}aY7yhNA@lE^> zr--{a=I*BQ3TNWgy{w+1nr$Vic3Us6qu1JN=Dp=@_fC3O%u<@kTv`+n`QrUWb1_VO zk~}Ql7gNMVaatS}yQCi)xs@{9$LNVI56uOCKJ4Q+2xERNzp6hfJV?)b7yMIE?Ra`F zyoKhO*QNRA-GXW1j$kY$z1poxrf#KN{1d5K-eA>BEl^i2kU>&63Zj zhna!;eLaPqkH?!V#t&=h-{=YXv_2euWXhNlrirN+KA_usgY_rg2C7N7z?&H_mfUR4 z#xuhP-bCHS+wYwh+4U|F4^Nqjx-UJiZxU|2o2ZsNEgB}Li1svxdow+2uafJiV%~gt zS)QdF{*x)K_XB2nyn1!rjHe$9BJux(tZ%BrJk9bQ#4DX;xW zwO?&FJ={OkhH!;iMtSe!$&1>hcN%Bn9pXphyW?BJAr!4}OZa?zaeSV;Cd}quino;0 z<@i(GsY)LjZr%Oy)E zizM?Uvr|P#H*qC#K5;U!pJF#QB$g!>BxWWiCf=f19la=SqjjQLqJE+#z1z7zQ8H18 z-t1&esQ4eLGN}?`3;yG!mP1cYtmtcm zN(o(AQ3_Wmnn+)m*P{m=UP^glSy5_OUP@1EY@>9nY@-K5R+Ivk6(xP;^(f6ND@yFj zODW$nD_6e4s)hW36(xISMd_VcQDSFSjU--U{^bRrL)ol(DJ55BMH#PIN%0#iO2E&m zjZ814OxbLsM6s;2)U1?nSp|ZJz8uO|51V_PpM#f@*I^YG{4&xZ&&9SYJ`Zj=UBm^p zQSks)`~4)V>-@T`vWVwcB}GqGvPS<&+& zD|#|yMbCn)=noPr`V+*8{^+pE;iu0y{h?&r8(#X`r$2gZqd#}7sEzW|QaH zmRs_GwL^6Q*fvPaXVqV(&u#(vFxx0cJS)mV&nl14dv+*&KilYc1}pl7!D_Hyg%xFV zhjLT$3%1b@99Hzxg%zc{W<@`XSW!+~R+I&o73IBUb(>6I&-D9@Z6ihcd!rv=Y@?rD ztSBchtD^ocR`i>XRdF$o75zSBRl?5&<=!M`vTdy2f))L~WJSq@SyB36R^!AuR#eG> z73IrkHCdElMZck0QRxL%^t+xFm1lr*Z?LEIZ zt1`iLtf(FYlq+O!wo#gGRv*aztmsLK*UKuqvW?Pqv!aCEtR4_)4@bok*hcjcSW!g; zR*S@GR_V|u;m|WQ+bC^0D|+5#MXAVHE%D2-qNi9^e+#9e2Yj}zkxN<8(>kmF?hr1@ zp3i%38Kl2KD$T;SzlDzd5v0HKe-vUyx%znvdd6Z!KPg#J-2+ztJwa~2`d4{>|0s}- z3d!)&U;OmBr{w=^`!fj7&FP3}$ zv7+)ntf=A(D=Hemii-QNI_8gHMXzXB(OVT(aHbeR3At%1FMh+{r2)koNZaWYpjxjf2+8Zxt484@KMo4mA)bZ zF@~4=@6e%V|IYvS@BDuh+0m3{Z#?m@&;RF80zex7r(%`S0KSj;UKAc- zTbS=f(S^S%78XkosWkNIBoj>@ir`CdNedr^``Ys7pnntEYdnD6CdzL(z~M$Gr3 z_jq=dHUQ>(`IztJW4;$n->|E+1X=)?@8!RM*7Lw~Kr^5z&;)1W@zUTR0D?g^j{P#dTPJPy1N1gG`q%*Q0SO4e z16;rXVixim@CWca@EhV5Q#((i9`^IL=cHY5Q#((i9Ceu5JVym zptT=B5J^-^G5T~&xRaQ0Co$np?!vO2zz*O`U^}o4*b00BYymz8HUpmln}ChL24Fp~ z4pd8-GHt@7vOcEGtdd>2)qWo3cLbz z0A2>#1MPseKpUVn@Dk7pcoAp`v;dj|F96R2&jHPVra%**G0+Hj7H9}O12h1h2I>P( z0rh|GEfPq2s{K-0Lla9fCqsG zfU>~-zwWCgARvH&RyiLOX=C5rKj{=A?o5?zt#ibPi=x+2jPiLOX=MWQPbU6JUD zL{}ubBGDCzu1Iu6qALUz6MSLCxGL?G2ke01UL*F0uBNPfc?NeU@x!-*bRII>;iTIJAf~N z?Z7r*EAR!d1^67;415M`0yY90fc3ySU@fo)SPiTKRst)4<-jr%U+Hx6n#O1>d45y@ zo$H_X5}o6nh=-N%&zni)O@+rM&8c!#=uA*d^?s?~ba_i+oWP`ltqdcz?2N zSSqSOAJw1N&;G6cd{lkj?C_dK)#vpMQT=&QEvi57y%W`+7rp*ge_r?F`tvnR<;3Sv z{rTjhs6bCM<`e4UsQ!HN30r|ap}*FLXlfnRpjWmEy_(B)=qISRy;*9g>ZiKXczj#R zuHQ)2qxn2FRaI3^-9y>+3#mLRdt$9hDxU&^F9+v?Q^Db2cd#|s5UfZRpm?Zx!E}o6 z9F;5@45R3V9#jgrZP1*Gz1H)FQ+EBTK{<*OC=nD2@=$L5WZ?UMBo6qO{qqzTaoFGO zZ}m5DRQz;*qCd(X=Dq9p_j}N@TU)=m$l*8g>-jY)nxY(KP`k%3k*wqw^7ABi`q}-Y z?^AaD%ksQDMfn?c%dM1Ke+6Z5m?tVwWWYo@in2KLmpx=B*-o~gh=BTuUu7-ItzUs+ z0!qrlGM~(myq>b$D;X14#D&C<6w`5(vfOVMn?$BqB^HbMVkTv|AL9-3dV8LeLH&a6PWcLk>IppedVw;snw|^sSi`Ll40uI)H{jqQo~b&QoU2Jr(RCI zm}+Y0o>Wg&Ox{E}Bub@`!{hoypIWYm=WQ7bfQ< zrzXcGM<(A)zLD&jd?oo(^0_Dq#`Vn>8x8Y|u|YZRO=g9tPbJwn?AWbnrrGsUW|HMC zW{hZ0%Q;-xEgNO^PkJxL@;MY_OU&U=G%Ha#>8MeThIJFh#tpf(Y05%?u*qAmq3#!1m_oC{Yn>UKlcOAEZjofpe3~bP>`=qzYZeG{eI6}8> z@F3a_cKd{~fq?E4@+E73!nx71j_L{qGeC-Uu!m=CaG84_qWJ&OnX>CWsd0w>!I`ql z>pAr-%bH;kovX&iExGjsqE9cIo_6_l<~6(gih0GhA2T*|&wX4kuQN`#=|0e<4%SwrLaxbkH9f%dzc(4nFr*UB7C?4RkSUH@dTBLw~%b(74Q=b+f^) zp0K=L<2HO&PLA5nHC@!&pVjwR=22Zh(GYaBB@K~*?f^>~mhJ` zPa2T7?VN4{OA3dwUr&ytZQiJF24yDb9k!$qc+i12pz$xe*$u*)v#D~R#Fw7U9cJ4! zI0kxcEwd{0g2F@W=2>lYbrd~j7hmU=x1^CYFgC=J;(J$HG%hL{$Fugp z-DF9ld7yX9lE(Bv4;L{-(b%@|?zHq2+BCvxPYdRcQBfab7gL}C7@KZM1B0OVswEAP zf=I~Eq_-mO`v%nSbsr4- z*>4Tf_V6*wqG3VHXTn=7 zRhZxM0Sa=Cjw^eJ8#gCv^|~*H*I9de)gJV5VYm01+jcu+zk0V_fFCvOJS=aktQU0K z`nb{Bx;UOTsK@NKk@k{v+jwu<{j@f@K`Koa{X%XnZSy734Rohkw$gCst^DuMc5$D- zDB-+cjBU2|mJ0Tkas%4*t0?MT3*8A6pMajdq1@(@-b~QPHg9arFPNjC#NUt?9N)H| zSEWJkG81m%IfFmR&xt>*{W+()Wix*Q7&~a$RN!nk^+wxv6BG}3n*_KTn#9gqdt+32 zb{hvz*~4mF+?pGqHo4m<$cMIDCK~@QA{xe2$0!0kwJwVMPEAL&cNFWLYL#k|s+X#f zsu)FIr*5UEtt=@&`5QfHolYK3?n-V>u1bEAd_OrQ9q~=^^Ia&4zBxtHx2M>3ivCtJ zqxf&NOzn?iz}5DD6a}s({#W9_)nG(|+h}+j3m;#jv&R?H9D#Z9Y4Ne~Oq$)_E#5&F ziMLcIx;B4EA(&ByQ?*tr-F&>P@rhDNq%t_J2e&|LbhJl}!l5k#{H z=v~39vQ;#9;O}P+NMHPxp7B8y@K%XW>>Q9Wg6hT7o07`vesx}#OI?m;6^J^bT6)fi z$S&f-rMCs=X)eirdRM@+GrWm3zhv;Ab4}{gypzgy{z)M(cQhBp{lmTFo^g-b`6+8? zqROBDoM}|X`ITS?Vp+Tn;v-fs`y9FDwHF+<%)j||!)m-*v^@6;W)$@K~R?mq7teW|oSv3t}teW_*vuYgFVbw@x zW%aB-nN>rvgw-=Ly}t(HN47mJ%d)C3US{=_C0W(*_p*A_=ZGw?x|j|dO{C=r>wg$G^$%k2 zyb7Wi@2|Yv&Z?YWi`9dYBj0F>TyD0N4MwoK-*3X|KK}z&WxNZl?)B@lx<}4sb+^y) zrC#YE2ixwFmsphw(pSly!8o>+49c;(!+(cW32~5Bae0bWF`533ipumiSS0ArOK%tH zBP|@PW!r7CJgY+T2UZ2e0amw)^pO_u$FS`dxs6r+pcJcof@A)?yfQD_@`y&PZVouI z%*!p)SNl!=IbNDeKFcbnxQ|tifTKIT8~q2_mfcTZ?KcR`wrqa-TwU*1U|Uvkjn#GX zZdO_RNvu+WBPG40|03HGe!9PL*^q5vkiNT(NI=a*;Y%4V#bAUmsAPzB1pCOEFp{X^_$+wZ|EtbX%5v-(x=b>Lo=IobA0z-N~p zw3@T+=b$&M%K>{S_orY5+b&6tSag5%>#*&jNN@2&z@Emv;NQthzZV>x=$`kpu zo7Gv-gVlF_X;x=~{H(qeD_NbE9;}GXTKFR8^xS7=<$@idpP^ORSfPaja?w7x@ z+UN7t?(UWRH^trKpX8;x#UNH+`Fzi~J7s!{9WoCu{nGEmYP*oEw)uwDR_U_(Lbhi0 zxzB%l+|BYCwtXh!tTy?tvD)b8WwpUS%WA!!z9!fC>4@XCGJPGakq)o7TBg6tRU&=0 zuk_RBYPn3G`(>gUZ}F+@!)mFYUb;jSW!qx^E>?@gI#!?f=_~AGS(9xaiCb7L6ysSf z@YDClhr#V^n=dx9`aq_?xA(oD*)~T!&1$w7z-pGSS^GBdjul^z|}KUS``+k-jg71nG0|X25^3-NCXPuQ$-Y!m7VN zlvO|fHdcLQWma#9i>&$xzRTR+q7vJB`72oU2+FeRCer7nt9YGlT||1l*Cqb~a660i z717C0f9D-Vci!T)K(Kn%=f9zD2mevFy)34&YVY@EMGvm5=uwpwJ)E*??SIOOoSRUFnXFof^xpt_rexa-@_Sa#d+9SykBV%gheTHNc*u$#{8-Vm9xHmW$ckP? zvZ9xktmri-tEc6ItmxG#D|!jQipFkO(I^uu8dqXP16izS%!U<>%CMqw7*^H&^#1-m z&qBEWf_WC+BR=1?UN!L^z6I}LIV!6EH&o_{{mZMI{fq1W{j2L6{=44b|BHHq{}b8x z#GCX+tGnnZ+KHB;sc0ac5H&;9smD5c{%w#y$d@=Q3pGdTaxROE0T-EE#fAT zO(f{;`89gy@q>59JK-Hn&QH#w_hjR!w#88ID{rf}k>VwmB>N|OQl5%A-n3-rB4Y)av4D#1d)566r6KJ)IX^jp!kb=pl{hA&uxEy*WA5spx9N1Zl(s zX~YC+!~|)?1Zl(sX~YESWi~WO(|c43(b7wSCCNpxpIki`t@nXBz-(X^FcX*oOb6Zr zrUCT!KHBvZ;9X!cFbS9lOaR6M zz#qWxz;D2>z*XQE0I`GWXS5JIs1Q4-5Id+4G^h|Xs1P)$5HzR|G^oyF^K-yi;5*<9 z@GWo}_y#xyd<~ogP5{S&W57}12yhrU1RMko0Q-S`0D=Y;Zf%92L4}|}g`h!&8(rZ> zR|pzZ2pUuf8dSLL6>fXA1-t$n*bICIYyvg{8-Vq|I$$lZ23QTO0#*VmfaSn4;8S2J zumo5PECN0OJ_bGl76J=^4}tl>2f#c4-bTHT)*N6qFbkLo%mAhX?*Y?*slXKAU0^aW z377~>0LBC3fU&?kz!+dOFba4ZcncT_i~xoMnLq|G3>XRw0p0`#1A~BpzyP2>&=2Sf zyaDt9dIPBQRs?7R}{LU&=rNQD0D@kD+*mv z=!!yD6uP3&6@{)SbVZ>n3SCj?ib7Wux}wk(g{~-cMWHJST~X+YLRS>JqPQ!?(4(S2 z5#V;9FmM}C2q*~L3KRft0rCU+fV@B+;AS8w&B^en)Ri zzC!CdAPbNJl0X8810i4l4JaT0d_V#M@BkNZfS5&a4fq529rz9S6}Srg0$c%p1}+0X z0hfRufs4Qozy;uY;5={+I179SoB_TCP6OWnr+}}4lfVh!IB*O&3LF6r1BZZvzyV;t zNpy7HpaHi;Tir9!JkdzxNYqo`h{=?Ftt!QVmrLBEqPls7#Fa#zM0T%NB9#ba$@sPS z&r(tKyl=b;@gwm)qFQ`=e3MueU#0IAM`=7hLtc!}*H6i%(VT*0m1tIhs1P3*??n~$ zXkLM=70oPAsrb|J+H!3Ck@!PuK>R-au(v9HXZ&{AB%WXAO28N%9Yt@YK({Q02LDl!B$}{1(aHQ8Hd^3E*%M81Quh2|` z)@qL^NhA6V)ylB0Zs*maw*i&C#o>c$n>R4LJ1p)^4hx1iCm#%N2ovg#&^LdmMKsQT zUX3=V^y9LnIc#=&+iApqgP+T+G>cR~_4;P`#!RA`dXvm(lOd0r0lK{Jnx3YU+-%yJ z7AnIuHcyEr=5h0|EMv->dsRDghrUg&F}JDj#dfOZccUtAQYMh~^))?O?4`PX->b*< zH~NTbq4(%*RC;TpUMctMMfw9dMb8jQ&i=cI`)Xc&xxnk}wfDw&n?UdAfl{if?2J_Wz6j44o z7(;Oe1A|^c=b(M^S&B7il6*3#AJht}QM^H!pd`h-<)gfZSpp@m1~LCivN**ZoK9Wv zkNSI)kNMmEO>&37n(F+0=+E-08l?XY)%qLc_wl><9ca#YGp{8@BGmD#)6BQ~y_pn^ zP{hAQZB*A(UaB+ruxhNHQk~QX6ccfyPU(QE7`D&@^rY13)KQ92*`C^zT9sPNwJF~D z%j|$wR9&(`s!ob#2mFV*0qL0m2mX_J0o{_XCR-<8&{14kI?lF({=!Ddysa0CS~2x! zH2Nq#-`e{`p$JxWG0$2qjjggAW1d9YN6^(sQ`NTjh6qxkF$>%N)Im5;CsV+-$A-6A zzGZI2a*fc?^zC1+K;XC@IZ4a==?Yhg{DYpHtM&$Gog=k6|53%+o?sCh; z8evE@I)b({+mgmeENOJb@-Jrqc0I}R&oiKB*mm!*q-9q<*>bs8$MPdR$a1zG2)o4j z7#!Xytbqo}Y<$v++P0aY5rnojMAYY3;eFQrif(GTDu(k$BSN;l(mib1O+Sj|0>N(c z^u4zIVTdrQ?)pyK-Vh?x_ElZTwmXIh*IE^PW81H5Wx2*3Z@JnSP`(ek6D>QdZ!PC2 zM2iklTWp)gu`J(EYb@u7O)LkhrIwQwqQ+K)=+9dB1#4duT(caeW=L!~JnUpG{lYeu zpM+R*k@?EDml_*U>Gm{hZF_Fm*>a}&z;dTS*xbkFE!&=MMp}+AgDrcQUY6TTN6Q(e zmE|<^qUBK2(sGKyksi=EBQzdq?FT$u=`<>7+xs2#e4l>TwrPwKbSqlYNG0g4wA>RM z2YvcGMsag{Jlou@5ywh{m}onQ9;Bg6+y2Vcmb-KpFqmaG-xb&Q>u~$iK&)LnRSmQ3tA<)0@^H8ZDYa`9{J1e3 zVmU$KqI^x^_W3}eJ2Zf8moHGYEDy)9-$NR1XrU@&?e8nxo;37r+i%jJooN64Rc^~! z3a4R`3T^wn0Q=3P0yNQjhbc5J;>6&Pz>)$DEGf9al0pkCqrd{& z|08fyG~90OM;#o;5mnT-DLBBA0tYN9L;&l_CoL&Fz>wK!!CAPE^^=|XaL)`KXJdZq+x8(YiLP>*`SBJ zg@&|kd!h5CB@JwYF3!(~?gzF_gWI4}*^-91K?mRaJa?LH&vhDD(!e+9;x?nz4z#3*0WkK5)X}`^Ed- z``SC;ed%rVmU|1mS>7b?ZSPH*AKuAp<2~m+{oj-I{jah9QO5hf#`)7*g#RSQ9})h4 zi|MT{}+n?;BzEqpk%9K{0c)8SEHH~WX zjZlNA5<(}+Meu@ZNR|1jtA|t>bqCE6xS8tmg~|m7 z%=F3ylal*_Q8cTqZ_qX9koq}zG5H2f{Cg_7o$?S=q2fyq1a}2R=<3c9q$m%;?^K=d zJO8-9kDlH)(%gZM{r72()mX0pJ-H90)N>vEHZ*_W8G34e#IHa}W=i-4{oKio{`EA6 zz>z;oD)lY*$!&7I{8WCJJT7Or>*NGEQVxSz|d`woNN_=<7g7PMLoeYv6 ziQm1Oz3V;gUUPqPzjKedds638CnyWRw$%F6r<4O=MruN8B+dTsnR+et63zX8D)m^Z zQtH0c9jSuJYsuNk$;na4A<5=8nnlJ2hox;w>)SPN|jJIdJDQ1Nx}j##h5<>ZSAIg6*mHLG;iMLUa#NM_G2I za-jCY@1hOF>pB}mXQ~fs7j+6y8d-K!Szvk1L^Xctc6n952l0wR&1R~wf!fdVWfc|Y zrFGito)GQy9Ei6135eF}GDK9J)UMo0*M)ddpM_|t-hpVL?uKY?C^t#8=NI%35YMXt z5YHLZHkQp4s^H3|l&Qq7+$2POXxZ4bhNY2O1@Wvw)os~OMYVuw&(Bb$8Y>#;DiHPc z3W%oyR4$iKst;kQt5Lj}swcb!OD$6k;&H>LrKYYA%VVk{L=DpgqPpQl)u?={{m_RE z>N?A+hAmZ811zenazRw28FzN&3i=I*^7K~KTFRMr5D#ipgO(2%6x)_%gP*bJe)SE+ zeF4gE(@X3{u-vP+L)@c|L)@*or=>N2r*~;S!cw|8*11#Ffheg^_gdbe?}DX7@HIs7 zFa=ReSBEI7PeByXn;~x3vmtI%>~{->1z@=~;H#y8ejb+msxL&o;3`C3^#DX3%I0Iw zMsBkK;wJMlL@qT2BBx10GnXJJOw5+(mgYWNqFb7V_U0DeY02^7-ZwJI zwo`s~R$q(spQw|PBdom>q8Tqe?w4V8EGWS0s2suSh|kg4-eLJA+Yb3~aKbwnlT{eB0w?Gx=-?Ufui@9hyBN$l+ovhmWd{D)ZWl08}N4Cb-g;SXf>Wx!F~-u9pp z+qQ|qthS0htiBMhu-Xzl!s>JJ0ISVGdsd$X95?T6@;R#6+ej6oBG)Or4T9s%z4gJ( zY+EOIHi@@Z+{m^yvJuU_~~=CT;h+0@RrGR&p-92@On%A^cG9x zRJJXa6<96u)93k$ znk%kj^}fu-YL3KDDdEkQlh`)P$Dd2#%?!%3ZHB;~H{nh9yRhv&zX7XhB7Gf9mCvzl ziWIEg4bodo7WcDlQeaq33^&90RL%T+TY{(6J9^Lk!^j0*IB*cFJaXuNZ-l5W%@k#3ev~YGf4Nm zhm7$S-GlVK&`p$PTURlXRTr5)me+%&Z0qbVX4Ogj#HyoApUKy}ACg02dFY-={{)t? z-_)~YGxSqxP00TFmXQCfKM_8gJR2)a%YF@Z+P%rxRmu^DpDe!^+$S=2Md2Hlv2h{) z#k(%#zvSJ*T5yW>!p9*V4Quiq28Pv=*X(D{5BU$a3@fLyTGP*F6xJ^kmP-}1rd?rK zm@0+$)9acYbYPd&F5cNsVft_`(a}QoAVs+KSY|)J%Hv8j%{1>PA`Dnx~#g)k;;NG1WV0 z{(sI?GUX+&B+pTvwLQr%D9758>`Hu=SdsWBF^g)wjZ6$obWe0hv`92e)J{}Ql%+~<1rxa_zCgyWQe?r& z_`dko_*#16pBtYN9~~bO?;Yf0uF~e>1ls? zxFDPnjt_^2{b}sKeb_vF#?Im|8WW|)4a z3ys=8Z=NbzktbM62{e6^f`T;#^bl>)ii5=4n5tE(r@Zs^wYkTZmjF-N9e@f zrEk}HbvCVO6#J6;Rvo4vYMaywwUBE1O;97%K*}lDLA9W#_}Y|Bu&kZue-r&y_Jd#P z3I6L~f3PiBN6+pb&@BIVf?+g}@OsdWGVDA}PwSO}`zU8%0eVuWm#zL4{~SH1@A1Fz z*Z7P4x&9P?G|lku?RWB9`%Ni7;G^_>em9M%=k;&!jl3o=$!{qm;7++oE|&}B44U0P zoH7A+mF?vV@)^njSVi7X&*ZntoH8jr%KdjE7j}7mGJ4kNeMr*9s!xz~AU%>X zE1gTO%Is-n?1&p=_OLskuRpW9HF^g~c7voHWOlWZ-ny&IE|7UhzHVjgN0OZ(sk@n- zu)mZzkE^O1C}9c2+vINVbKf{${qp z3KK}Sw$j@{@+GXWkYp=Z_mF%M@^g|cA+wNdVWs;u$>xxEkbJ?)*fx^SLms3ff6gBH zOJr<@Rp~CtYzldTWE04vBpXA%MzWEWPCt^*S{eI|WJ6f*Bl!&0{eV89f&GLkWPI8h zy?P|;Lq1OODM-42GHJ>oT_3RzX{#sgR?Wy**BYJUB%iR760rrDb?ovx$ygiPuOe9s z%NLM*9I`RVnpQd!Nj_#}Y$@%ahTTCnGCpdJ-isuwW2=WrK7y@^)2h|%s?nK!7|YL* ztcvA5NmhaEOtP|-u>rJICA(EuQWdRwj21tHHOZx9RN1k|LsF`+%=@g2T_9NoTSeE&y^!ydgtv7r(!R*s(v8=GjO=Xb9?XQZ zja{MTlPphZ|hVh32*B?N)q1IJwlSb z?e8Sn+s>d5BX3I|7AGS++c!zFv;CMPoUN0cRwZXktG-M|_*$1-T_*WjIvDpdNjO_K zI&e5!rwvIsTPF|g0M0hL$=TVKBME2g_NV3KZ0Q3!lOkVBD(VZl+Gzje6Eop!=_i!R zgs+W7_ZR!xkR*JqN4_=_zSiqO628`}M3Q}N^nI|e?Lr?$zLq|WTzDp2t#dC)xLR*N zNw``sNfNHsjeHMWtviV%TrG{9sZ4gYSxLgxx_8niz|}@OfUEVQt>9|C=zNo_jlR*y z;lS5A(N^SZ>9`t_5w12C9SmHpm)%tvaJB9llH_Wm9sEENzSg13D+9jP{hlOztvA&T zGRW7`Zu5~5uGX7K60VkN6$csQYH4{{Qsik#-Aaqu(RL?EjyC#24%2e-w6wevDRQ)= zo}$Ib$KNfLh6rE4bxe%6^w5`NaHLLWwc zmOhMpTn4*Yay%Juv$2`9oZKueFF{6lS?79^NZ+z{jBilI?Awq zRzxl}gZyl?y-(6QTH(e>T1P8l(YdvbmNG_>Bu7i@+6&2gS`k}FR_ken_XJ7nX@yIF zT{Enw6<%$UY>!;gYYV!!1ij>uLoJNU03#YK0qJ z3gl{|9Yo!=zE(KTk+i;6c(0SRzE;q0T9sjat#F%=w7yojHAz}uD;)aKmO;KY>Q9QK zb+*DIN0DKjZS)&iWstLtJ}vrAthW^oIf4x9ZAEN79WHrWI@~N|w9Zy|J4sq+E8J5g zt+N%BW?E%fXDghwv=up9+Uiv@T3;*NT_nlZMxXU0N$YGy^xG%HI$PlmB59qih<@s2 z+=>Gy$DLult#A&JwBA<4PLs@!okc5HZ!6sBtF+!$xQ~(~ZyR+zy0)#m70x>(t-BSm z=;V>Rr4_t8Ns_;fjx4&rt-}@M1A`25xYUUrq{!crDnp9gEvcJHk+&s9PA|hcTjA1Q z&kXBqg>#Ukb+*EbzG&-gg+nelgPd)2RMEF!y{(`>c`CztTj7-=X}zs*qZO>T70!H; z*4v8cStP@HTj7i+X}zs*hLN=1RyfhQC2t$`s~1V@ZiQEbq;se=D54 zB+1`Khx4v$!Y7=%{}c27Urzoz1HfYx6Hs24(Rb=1x`58DZ_r7KcaN!G)J653I;jq+ z-D(@XM_8?v(wl_$DX0EKHJbj24^n+pR~k-hLp2H-Q{KJW6d6#7hBfY{8U?qhd@85P zO3?u#_?>DLd{0sS$LRgSPKpj#AFK!#1@nVh!IWSe1uYJxY=S*0%Kzn{Rq$NUFnE&Q zHatu*{`Uo?f};O_Rsj50Up4;|83#t?q?d97U4ywq26KrF<`NmqB{G;xWH6V=U@not zTq1+HL_TfNumkuK*bZz1wgO)OTY%4j&A?~CCSW750ay>L z1J(j-fYrb%U?s2uSPm=$D7-G}*iv8#uozebd;)w7d;}~6762at^MMb5dB9xYeP9kS z8=wgH=+kEcGl1#9djQ3jMXO8&rU35(lYvRVL|_6i9vBCV1>OP10Hc9X07ddeAM_S5 z5*Pst2Qq;SU>GnI7y`Tr3@w@2V?VuI0>8pjswSlqreg1FmMPs2pj7coUpuJ^?KVi#q9zbJFrO!4F8@%Zjk4Jy}+tn#(|s)=6y1Mx;ww{D2d zLcb%*NFTrCU-&a?eaoO*FfU#=n3l+G^V+Y8vfbPK@g)-*=$W<`RRma|&eK!ye(lop zaXwv*^2%4FEct!);lInFf6-(~l`wwTKr~DY_GWq$HEXwkY#_s!U?0 zxhz~!BC{Mpx8g7cl@y%ZS8)X`87&J|mwrdpI{rs^P&JEJ zpb7+6)nJMx*seAtTE`cwWjec#$ESJC;$taOgNmPtXGZlK;#>TWsP@2!)VlaQ^QwO} zoNOv4w(HaG(bUqoNM$iS^q6>cbE_#6UXW+QQK@=iHZ#!6ZEHT{((O}C;zy}sK|PZz zQHW|l+?pC8r>6!+bs$tB_mcm7yn~lD)ykBxRSxFc3J6pSA-;y{B9sbiB_>k6gtoSd z0;(#UvNacq#akw+gq(MOSjro0=KCu|^Td^~PvU&yq`oMo_-(!ViTy#Q7{)pNqpPs8 z*enMo-jY512mBpW=ODUT)_4ozb*VJ1{qLTMqI4W8;zTNsD_yE>5xsdMMFo>s(VPh= zXQ=rRDmGD1WZMV&SGG;2w=Q58me9j#<(C6DVIVVI}nj-eRssET4?MZ@A}97KJ!P>BKc3ffK6iyv01q zZh`$>r-rcY12vLuIjFuSmS$Io6O+#M0S7HPu`n-hL6KLyUd1pU+dfqF*p^c_VcSRM z1y&1<4HTf@s|D%=ua{r7WtCUYWtB&zKVy;Re#Ab}={*-vi+H_TbPcH3V*M@KZpR22 zT}_460=C_z`J0X{(H`5%=$lyGt5U4)p*MEu`Q5<R=zQ_mGaUs-RZz(v4;WtMw+m#X9vC zuUAVtU=RMPh)mvsMtB=Autlp+y6gXFdO?Osrm~O15nb%o;VzRUPSQTP5 zz;s|W+_Yyk&hQ=Y^fk-bR@y9Mb(8s&)gtvatJQ_o>>- zYN_VGp-#$t#3e3ismx1PnsHFE zYo-$0ikS3Y@ilrU+p?PStf*u#tF?y1jGZwCk=W81WbWdn8^fZkHmKRG)|;nU)iG&r z`@h(G_xLTQ_V4?gS+i!=%$oB&+wUap_mU)(q9`GeO_D@WL?xjTLK40^`tHgu`;cTG z*(4#dNk|lJq@7&|NyznWgqtnd>E0*J%S= zr2>nsTuoliR++uG**j&8Gi*)S$|lq?z#-O1f)FTF+3_>Me&@mv?#FceX1y=}lZ!xz$=AyE#w4s(Q+UlVV)n-9C z5bDE%0uV}=<~L?`gHRq6^c@HVKtWqTsKp8T6ohJot%>|)iBroS`M53({Z97aCY5P9fgSM$?+h|S(O#-bp@50tU7W7dh-=(+8d?;(& zY@Zjp(=HHt&_2zS`Q1JyZCU%Uw0&c?w64 zUr(t1XW0K=@@N15OZlhsbMtrREAj;xd3_10t`E(hl<%AGj(Y2@@(uIVQUCAP+;_Rn z$awxP@|{2T5C8wP+-143xwCVFa>wO*|5Ez*{8E} zvv+38vo~g^WG~L1mpvnUVzzI#d$v=yRkmTadNxddO}|Swr)$!8(^u1l81a8E@&^{D zQ`3p*m~?o0>R`1mHYm@hr*OM2LCz1y+(tk!WJ-Iv?myAdTBVS+dq-(MtekC_RcD^9~CH^+v z6t9k#KC^*r2E5 z_wGf}$msNFK-356fI3Dkq6QeLAB4Yz+r!VpkH!B#FT7j){}aLs!n49tkX7%nuyfc3 zPhx6@M*psN=&gFKeqXM3M^n61jx4eClY z{$Kq6d#P$l`@3QL+vKnD-xdG=ef}+eNw9ZN6JrVfz_z|JWw7r#Cm+mHW$^Dg586CG zDuaO!?zs_YWdEN31(5xFeh$d~Jr6FZGWPFzhhi=G_plb+W@T*O%Pa@7eJ=x3U)d=7 zfDF90Ww7tLj~xVL|DGzr2H@Yr2HK$!_C2>RaM6^V!;8YVRL1^2XVfVZ|6V`fFj~M{ z2NeGvEIDQD-}B%*D`Wp&=6N9d_td*U_V1~Cfb8E>ZGi0G^I;n+WB;Bi1&V)f3-%HI zJ?tYKPi11?y8Xwr8&gK^J*^f2$-Sq&Wk7Q8 zY3#TuBln)p6a$as(|iMv+=VbO8EDJAos_OPi^GVf_M3`piZt)PabWZu*M zuRt>IY3JT7y^Qw2`vy3PcRT@3WIPu*fiN=-cqwBk@Df6G8c+lm?#)sbT)!_6g6q!0 zVL(<~4?gcwQC!GqsVMF-KvrDU5(ve0tK9@-$@K#uORo1O4j7V)1FnrmR$OP)EoH^^ z&j&(r-3A5%M-bw##i$gLi?wi(ma^h1e7~ivxXK+FE3R|rmWtxm2a4jl6C#RRhHXJ{ zu`OH`r7XDqYd{uUzbB9dH}f7Ah2Ub*@gV3e2sczI%Pp?eQkL7y0wBw623}*#b2sXDXZW4`j9Vz5=q^dQE|>w*D1Zi`BM0 z8d+`C8$ecDe;JU~78$@ysi^G@SgSYf)?hTU*k&MPr7X7o4?q@MPhnArEsh|IM$uXb zLMdylb1auaYuyoSz@iXaEII{^thHVW6s?8)m9o~V4nWab+-s$*wSIFTYpwH7m$KIS zX8=WOy8u~h{b%uE&{}-herOb_T?K^Hx+@26;ZoLG?;;>;ZRT^V1+B$e-9eCA5IT-h zR$AYk4Ja+v_x=DvY8`$7WUciuoS+n1i?w1jveYVfh%B}KCBPPZHZTUE6k3Z#-S;h0 z>kf;h)*lKKsdXoYr8e^-kfkRzPRn z<+l*XLaW?rBDBu|S!g}n)iWTpSk?_fn88Y`@a@cCrS)*`W{A@64`ikF?gg^adJyOt zth5*`U}mt=DtwbOSZUQl3{8a6;?PE;k%iXp0c4@|TLW2Wy{~~RwBDsa7FzrQH#0cEVONX$?tx`Y;tvjW-@@KHpW}d)WP+F|zK7fT*;cA`1LaUw!ve2rVfh@G@5Fmus?ZJ0I zR$4q09Y5;W6n8(U)Pb^eNLcXVQ7;ec?Un?QzpENy|}~ zsw+nGPg9SiS46$kb?GJP=-jyUoODQTaeA`prEB2#{gGj(^w4Oqej@Fhwuif*g&CY1 zl}$Dt_C-`M`4UfU z+J9u#HWDZfon=YU(v>Y({;Ecfh5(H2E&srk+kVM91jC$;ZhGwKjPp zx<{RnEKZ))N%B}+piW61Ozu*}$t}qYWdDbIKWdO%6YWsllgpw4y*s%mx;5CHjEbk` z9!!Rr8hS@^npvZtO9muIqn1EVvoWfX9FpuG>`2;}^TQfR6Z3xVtfYQas_#jvnExXUj5vx;LBiRuqJpPu8t+abHNk24Z*|60eM?chWdBc23G`^AOpaN;EdeIcnWYF z>fdz_4phB^eS;SG{azdSAhr4(zu>=7oAu<}dB{Jo1K9;6KQ4b>{;d2U>g8+ zTh$DAk2~m^2_=bEwG!U$J_)7RR_E#KY5sZMD->HNl+jq#hzn_Yl*c)Ne~tMhog6&N z?a@0f`adO)({#&t47ZzODRxU1(EK0bBWT*jlr~o#7nWyjtB}s$`3KQ-XS0Xm{)W@*?l51|v@)A19%3kitU6yV^2X*pYCpxhlVW1-qZk-I zFB!w>dH*msx^{n$p#--I?~!P^z0E}ws~NtSM*FAI^aaVu9D^r0di=dsH#~o|rcBZJ z+k}s=uGycK8*NW>(f`>rqqx&FrS=|XZ;Gh!PVop+lVZ+fDb_I=il+qpy^P85@g9kK zDDHfX(R>phgmF`DdcSBp#ewQJUN3%`;$G2*E+P;0N}3Kay2>rz8W~t!`%f5Y<)&>! z*AtfG;~S{?<{M+j(fomijzf)A^J#j2jLUNCb&Eszb47_y@(kLcWVn)e~qs4!0V*z6R|>)2Ai}Q_O~+P#hHS_kT$EC{14& z%%)f^*Qp2_TzlD=%J`#y?^v4U;SynK9#i~bMci8MXfE2h{ZBvDp_gCA%*2#=yzGd!H43J;@LH|$PvTri$u z@0hLwHO^d0({;k;6i?T5U1E&3qUnopWw`S>Bv{DN(XZQtXjg zM6tVmhNI%!;ZXDrZnNFILnwCj`E(uPb)@No)pr~->nR@O@$J_|KSI-86o1_ZW_HkY zXRi~*1CXT8ZMReAZi@SR?@;XM@oll6;>)~4@F^Wehru*^U%iE+no6;KhSj;9=Cj#O z)u#5giob<60e|~#GJG|)Ry}EcE6v|xD|ItXxAc}!Y!UEP+9Jbey?MY_O>;Gz<~LJ@ zVp9zRIL>ZU?x~x2d=WPeDDk^$toX~?NAE+6@8h%BH428(YK=~#=DmHM-B81djjc80 zOL{MehC4S6^cfsI{_g4r{2kQyUZnPVnR1GCz0)by@%YlJ9mF(U+uw^~E!B%+O^CSL zZjIm%jvgOub$uXBSI156=2!DxpqLL>;_}{;G@bJ^w42$9G?{9?!&1eHVZEi)p6ItY z`ZFlTitii~98J?kv7$%8Jv1Hp8!3ir2}P~>WNGygO$Xld6qP!RqOXT@%d zGRM1S|Bv7ej%p>vJ;53`+uzffCVy9S0s6mrbfNpd2DecAuZj-V->vJ@^ltSO#b127 zG5lSb*J%3Z%oh}QdTS{DgiMw0V}H~OII4COf5^g}+^Wf%u01O%H^Z{XZqC$MoY!jXe_aA^%H1MZDYpQ}TIC&+z}0 zbRN^!_*+jwnCv5|JmPqN^A!je_?x^bh-dqMN*0f4x7TX0T}aWhboA<6%e_%;{AzOZ z&#~OwpJ(}g8gIr^`}lyDAMdeBUu3&e`$#*MV%G9^*4485o^1IBiEJO5f4JrQvXSL$ zrKvqM_be)HNuGLjlx5jyV)v!x53qEp`ajw_x$m%?6ai4ihBW_B>rt$4`MCNgzti+F z$!{+DyOVG6=G*v^^10jvDk z8NT+1Cwwap4_RJ^`+N-#i?5{Z4vTodhWW43^jR^>-dW*gJniv4aAw3hbY^rSwV&y) zr#RH?;;3tIRLdxykqqD%-o??MKygU&62+r5ALmi(W18+01|0oq2}Ub*3>s(fYbp%d z=6=iVKrQoixs|y$bBl8ea`RBHd{z!_`P{VJHGyDf{+DZR4$A7Tc)`CYpgL((>tK(Pta$ODC z|DH=8OXdW>{e7nYpfP;z&I3>lF2LX79r3n!o$e6)b?m{cxI{O{h=j@UIE+jflHZyC zI=|65@$;|dpCecO?Z~4@4*LB0!ASg;9hmQz@1+~$56*XhzrF!}17lo6+?hr=)CqRL zq2-%hJnq4{{gK1@iC_qv|FcawMgUyxMg`!P^dO86I1<0BJE9gqLsKKzW=ymPP6CXf zh*sfm(Ips7Fdw-DZ$+LT=Oq{)ofq_ohW=yp1;!#khcOZXwe2UvRj?Zs{I&$0!%u^8 zA&u1tpQ3Ral^DMr;cm8j4m!)5x&|e|`^HTn9emsZTpN{XKc#RYzdpV}t(q9x8 zQ~NFPbhSBC%|DHQOZpea*HZf{FiyDb&gI+9pBGbChMVw3YJb(Izjpjfr2O2?@deb5 z1dkN2jn9m7r{_+}9h-ePcSNpR?tomoY|UJ=jJghDay}maj6lR*t1}R>*J=#*VqLru z(fHVFRh+5kuQyj9R`apfh{o-;dL+{rV%MEMWrJOa>1zIlkZykbL7orX`k%uX;`V-{ zh~`LJm!`McIuy6apT?VQ1x;_VSF7JM_0+j$HX_!SpG@b)O=$kM+1_xrjF>#~dj7&8(8tEgH?;g|R#rmnX%8j(bikIF;AsCs9QZP*3TKl-Pd3K%< z3d9JZM2rxM#0a5Gj1UUN2(_>?n7nQFW@$t5F`?SFPzYsIgu2`7ggV)2LK%Cl&~Pi> zR3CNnqzx7JgpReBFnOEoMbg&CUMPeLd_ou4(L%$lc({B#CXzNhAQHlFdm;2ULin91 zgvUNY`0Xl$U#~*={V#+*P6)sGh43p~2vu~1P&h{jrE-MOFAAX?ju0x~2%+~ALX{gK z)U^>pB^x2st`S068X;7o5kl=5Ayk_YLccA9QfERarY3~4Z9*srCxp^+LZ~n(gesRp zs3s_c%9%o_94LfhfkLPgDTKm+La2Z!glacJsCFoX%6>wqmDTF$nLMYKEgo=|ws39qY zYLh}J-6w>~l0qmmD1_>gLa10Ogc_MbDE}vfLVrRiJt%~ls6wckDujBdLa4whgyMig zs9Gw7impPav?_!etwN}gDTIQ9La0nCgc_YfsF5jz`kz85WhjIir$Xb*IH3!ZFNMY$ zaq9YGqOYWFbW(MVpO1>Sth=LZb)oaj(?aLQ6NN_FQ-ww(8-&g=RaeQ`$!cjkEB-;~ zOwEAf4Wp0P0>OT65*IMSbvn_;ByI81=Jx-{#`B4a-Y@wD(N1^8M zuJRthr!CYpnI+W3Y!_;roGG+V^0^TF+(LUNRiD-nHLiJkdnJp68X$)@x7D|`gz6=| zh3dw83)Qjpgy0Vssu>?81gE%A_2g2aYDv`{nop{(&K&%@{At-3h-!r^_+pXMD z-Z$}O()P6xweY@*bk1h?m!S6reyK2RjiuN1wpi&Az0Ec!b3czC6@uqp=(D8i zI5s4rX5K%2>Fe;$lDGV6e5=qWcx%aRYw!z;3C?|?j}uX;zxD8c$R-o)^U6f7Fz`M6=xyT+ZIB}S|Jq86+-oAp{el$La2}} zgo@2VsKG3R`qV|F zzrbE)FR`QSQ1buxum@lSf0h6L`(#VuB$&Mr2T@Z%4Q_TpNCXd(<}=X1y2SK26qId!S#6ie-Uy&3<(AVeS&U5N4)cI5afe^DnM>htJQM#ih5Q( zqV7>MRT1hxPEZ%9v(zc-7R+F~ z0Z))j5qvDjNa?|VQ$$9Vz?%UiBTL}j>_BDuQd! ztpvko5p08Q58;gMoh?kc@+Y zf=#Wc1up=zP7#cQZdtcuNo2iE1aeg(og=yq%ikgS7& z8!Ax*>!ACvdO-3H2Fjg%@(u>xE_@iggE)^Cjn#Ow+ks>q4BUutK+9rLhRsL3|j-j&K43AdE~!p9}wRA-sdwEZlyDWE~6? zuDn9B4hE_{kgS7&8Vw}tVBmb$h2wYwmjTH-82Ip27Ls)^a9QIE$vPM~YiQwUUUnpq ztb>7fF_5f-f%DQ8j-p){1A=o9JF`D#!#IdNbmc7y$vGIPeSssW)x%X@NY25)`PU1{ zIT*m=7Zs9oFldX$Vf?AL1IalU__*#0$vGH!R|Cm87hK)%7ie+Af+w+k&P zgl`bXleYXcJxZbfq7F@Lr$b#!b_X=5X{k^a^5M1odMQCKbbtA+IS#MFQ zAS#63Vm@yDLY7+?c}*e9tttmL;T``JC~|uSkmXj*1+v`wZWl#v4+pZ`x-mqBkXv`? zqky8fn}MviZUk5%>#ZA)R>*p*+&vr5 z2(G*IS#Ujfm@K$z43GskvlOQRf{W9DF$IOtTTmYm70J+5{;7c!HwZ$kGQ^0DA;n$`>)D|~-0jsTl5|Gu_ zRi!9^+PWiq1PHkWVw|KYV7+w%6AD;wy}m%!+srjU)?4*3ko8vG3xwXfEx0`&#@ofc zQNV(mxdXck!Nu-o(a3u1tlR~xx5^zl>#ZBwP{4ZYyW@f0y3JmL4M1?Q0e57qw+dBR z3RrKUN(Incwujs-<- z(VG{D+};CZx%H0(vfO&Jfh@Pq!Ct^}i$6C_0n4rPW*4yB!uf0pSZ*=S&J?iRD)$jA zxBfIB%dNi;kmWY>Gq5Kg;|oBRTUV{6faO-T0LgU|{fxb8f=vjfF-+eH^v0LjHE>yJiOTsKgz zfE8Eu28!a|ghipaSoAm$1Q%p5TlBUF$a?D>`vt7Gu0m9S=#er~$a?F>MixMC z-M+NKW+AxPY#TJP-m1w!=&jrB5kL{#)3GQ77mJ<&YC`1h5SClj3n+5yz6Fun_kk?8 zK18R0+c6-we>?FtF8ATkk!_6iXdt`5h!X~ z1{Ae*XGYZabD*g0@jy{qcMzhsTY;>$-gF?;7AL_C(JFx4IvfLp-nvsa3dn-%#XuI^ z%w0ehTsOkBKm_+dpa||0KoMMbZ;0T!n_mPMe?b*s^X}7@07Y=8_`G@ke zQF;A_{FV9f`E&C_@&odH^4;LQZjo=0&*yb+ckWw^|NkVn;xFU>UH!kmj{k3h-*y(i z|9{SYliis8DEoHye-$DW&D4cy}@2-$HUP-#124Kx~lqr)vQkN zRxa7}kNSV}lY5g{NpW&*GAS983`hOHi;doJNtX1S;)&jHJTWWj)p}8qhpbe|3GBpZvqEq8hPQ)@T+h`_+j`~xHxWU^6SYFUrk+F9*!$Ei zszhC@CaE!MI6SERR8Q4KwSzIPHvSg=gMR<7$GFvH{v!X$s^9;~JNR!(xYu^WIZM!M zyITQ6UP`#z#wa9H!riuqzb8t#+s@$LFX3)m;btn~ZrlGI$lbQz#*a$SZ9@^DekI&% zD|Bcj=(X_)suC#OHhS6;>9(5yNAoV=ucs33wmoQ033uBbdfF1{w(+NB33uDBcwh;4 z+iC%jyKR3Lkh^XFDj;{;ejOlp+iuKxiFDgjfy4M7T?FKA+ef!tf^OTLk>h~eZ+k}r zx!?BM0=eJz>I1po#sE%J!u_^Oa8QDN+ui%n_7d*6-H4zPblk4>Od$8%ep4X#+!_3B zQNlg9j~`tn=(%ws`=OCLZspcO$L%)o4G=xI!$KgsZio8=x$kzvX-m*|V*}06*pKJ$ z1WMO^9#FdOZ-Lx(E4Pc%b@#>w&~;-2eL?8CK?h*Able#rcie8UUI};HZhU$Pcig@^ zUp?t#Jq&s*LC1{^9E3*cw;u#@zpe0BS&8)9=+jEL-&R)wx!?BjXG972+nL9J(r?cL za=-0{c$G-MJpjo4w$}#8{dQ&tko)b-oj~;4IA3ZuP&#gR4RXhwSqJ2fTeTNM zp+NNA?r_{U!JRh>XPFZ2yxr*k67Ia+$juV&yw%=7?z~YhASyxUjZbi2HTT_a7-0$b z-L58D3HROp2|(_8i#{Yq7<{ay)cib+UM+tY_Zm4_-cijG^KP3ngMgPa9uJ(%_$!b#7Y|`gF>9_HF<8u+>qf;Ev*s%N z-BT=@i!)lxnu~$RQ86?Z=LQ#JF-xvm4}|2pFY*`6hvs7bIW3PJp#yj>k>i~v)+2MfULJ(J0SEHXEE~?Py`p(ZLtWh`!p6@uN#mB zH?timg8Mplmj$;I8d-4FOf*7paSZN+vfirUK+)S{F&}!1`6qx_ZUfAQ++tUJci^nI zp8F)$+sscu)?1g$pcs0K^#}Q3anHbuv6d-@*t*MiEs)g~5C2UutF3=E)`8k$orY*+ zvGo@KMQm}~6pPqi4P>!Z*8*8=T`HGi7F%Rkh>9V$*j65mthGMw(PGwG|3e^aE&Oz* zn6=g$0%Wa4hJ~mYT8j;whenoKH<-DYrPhtYE*7a>4rHnIAf?5ST6fLg17xkuYzB(f zjs>#TdT#+mYwrNE)_T)`thJc}AZx8lM^emM>-`86t$iNYgwLp3mbF%$0c5RJ)qv1i z_l-e7i&<=a_cgQFqEMeaEjEDPcg3Q$=p>3+YrW5aqP6ZzWv%tx_YbXg8@L6?VykKaA+~PErvst34)Ld3 zvB>Q^K$ct29S+NF#+@dXTUQddnB^Avn@q9DE&A1BmRnR+GQ}*n>LH-WEegLDLvC>p zEzyXt(S7bXAgis9A6iAMw%$r0tF8AsP}KGkAggWWB_OMMurUjwq%svbbrT9?$Ph_%*pUoN!P9ilr2BDNO+A+~6B8MBI5Z8M((S#41V-4sD> z-7#JVgxmsACEXN>-l7*M61~NpUc`Fqs_hnu-rkKpXT8N8S=5s^0Ff&a#jOcs#dX;o zidb>Ig+M5-`>?vc^Z#S0bSD2xenL-Pm1$KDF#vFqn+TZsH!8W=ezv#mVGlwEfi||O-3y}-vNlZq zNOmRPCtoI?!-eo6vgp5#TmcJ_iSXg%zT}RiGPxPO)ty&E2yvbZR|4mqzU z!aFkxW8F`WPlluCZ)y(ypRNY@&$@+wsb=_pxP~Fd^8DxDf6l;v&cOdK&H$AHO6zc_ z&7l?tB#^+{xU>d`>Kv+Z$aBbX$Z|+ISPlsX3=nr)!8mmnA`Zxk;HET(fP>=TbMQD| z6fBmse{k5t;dc(darl+PZVtb2*u~*z4m&wu?5f+?j~ssBu!F<*9KPf5tqXzO&fyyl zUvv11!iAU;Y|)m0OFQ>gTw1CD7%!y zYaCwX@Ct_|E(G>v4vRTZNv*&x;wdVr71$TtlyAADmVJ(=Fp}Oia!D=ACAI7VZhV@< zQyegk+b#J7hsQZQ#^F&8^Eo`iVIGHvIn3qo5QhgjJiuWNhx<9)$KhTM_i(tI!(AM> zq}G4R&-wN;J|UAhOyn?u!=)T9;czjB@f|k&f#!2hv6KCaX5>^nH-QYz+F;ja2UejbPj_#oW@}ghf_HWc*VVIzmnIBejsp2Ip0 zYdL(%;S&yPIIQOIF^7*htm5z?hm{;Y;P5_&_c*NJ@Gght9NyvZHix%3yvbo1hc`I9 z&S5Es*Eqb&;S~-`IK0eZF^88pEaLEDqWgHmRF90_5_ib8i(9JMxn{Y&f<3ufrc1Ci zm&UX6-{pd+j~bf&EzAY0vOmUs)FNc_-=wx@Kh3UG)3fh{Q}mqdYw>hFA^T!jiRyq) zV|;jr>?3BoUX;B*9B7tj?}&Qjug+G69n`vPiRq#CWT$7R1dEVUWI=vXc3iYY_0OIc zSE@N#yz|x_vZqC%F3Fx4cL^%9$7Fj&d$L`lv8d43Dcd%fo^6)hE0~b2Y4+qdWNlWd z<>{~C_GoDOgBcjrNxw=zR}0cl!an&M(hs6WL8J7oxQAYszLGv4^huwL=IGF6Bn*b8 zx0{hBOw0B5phsF1PS>;3Y2hmEG7nr5bV)~tEme>7oOntQrbEm+U74PYx`A`je$jk2 zGCeYy9&SkwO*`v>>Av9vvnXv5O$c@(N8!+@Ls}aZES^oX@z7vt zzrD%y5Bl5dqjkCqQq1)a#$qgUP7k%?%$8uJJulj>`q;C=+0hg`$jsI~>Nd`QY`x@{pic6AG+S*!q4+LA4rSka=p5V;@9CDw zvT&qcmAsrRRI8H5lZW(z>;ntfxFf^b28S28sksp}+@!#a9-GTtmuCCT~G z()?q|+2QhFN^*K~l5Uh7YwCnyazxTC9GV;uRq81iUC}()k~9pLs>-BR)I}{#vZxZW zIMLzGpk@5KnUJ3q?+oXw3Gw!5d6bK{Aos%3c$JwSt-_ewrTG={qG+d@k7pfY)xdbZ z=@XW4asc&NOqpHOBk?Q@U-#7&@hXZdV;UZ>K8?$0`jWVm;@#^0_-UdQ|EpU_KBe^+ z#WYY~-4JtjkF5!%Gfh1YO>1k@!VuSqV}uo{S=GLQi@NR zS18_R_&mOftH6D;CGl2@)6Lx!Cm8<5Ux7K*&A$wuDHk6%lmj=dGQX44;jvq za;fEhPN4Lb8Aa0twhhJS5=C*b8KCE9j2b1m`kpm>ejhN0 z(fk)oH;PN_!4#h{wJA<9wJ5GJIf{3}4dnK>(C~S_H~N*PXGgzKyfXSp@5JX+W!YIC z@%g*P_M-WpN0ggLeSpzKZaZ&Bd_HF(BZ!;6IC`Jr9ntb|o!ei|IjCMv>QVd034iaG zMST1pz>VbAyE@{WhIb>Er<wWK&7HBj92XHheX??p{1zJ{?; zuKm-fKE+iLpVvE)^Rp$6vnq$-9LqV~%EFzr+Nhwa52Se!-W(lh0juaKD>uwLCDu-ndS5Ges}}5mxO#i9}1_@ z^h-$(igQ9fzpp{d-F~bI$5VVGq;w?e)^G$(-yaU9czt-9Y2>a4$r1Nq*pu2fhJ2lE zvV6Th7xMYLA>{M7DCG0BC9Ffs7hAr*Z%_FAJdyBrKGr{*vF^Af2jgSOiM0GW{T{^$ z`fZBW>DMW4({m{<)b~;>*L)u4>N{!rc0JQf!SKn`) zv{&WQT&%B)`@7{NXW}M(H7&PNUq$gj%hOBrM4Fz8KGL1PhxIuW@6~5hd{Gajc#Y=s z_qZNJ(=+ryicjm_6kpJM{LgB>j~7_}-Y(IdX#T|suV17&E7dH`*Xd;4A|8pvDaaay zXjD}WzuKDlB37_waI&frauq(KK$H2lcqQNXH(|1&rYJr4Sr*a>-`qgypDU7 zwcZKT{HZ#G;wK7!i6dLxCw#H2$!w+O)dAo1tJN_y{jtBA;zt4BBp-SB&4krfb)e=C z{p%>MRIgI}K((j%zQ2^>dzrNqSHRBWzQcEe*0lP&y!GV)OWJaO0nL6VV99<*9Z%D5 zXZR9&D_}`^OEsbPH$6T@%RH8jH~f#O{q=xvf!7sZ3QGgN^_QxRH2*b4nT6D=0e)2C zC|;dH&94M?IeM&EOEOz%`sH95#|;0iu{gsg<0YSWYmvuC{bJ@O+T075)8fx(zNGk^ z;u~wBDxv9TGkj8>@kY_~0{Ky&oD9-arDL(AoM{%zI0L6zg#kBbc`L1{%IE9)YP>m?g@fK3N z-{WuiKA*4Zdo!#K_oz8E|8D;ligyKFIeG;Y@AT_aobB^nafkYlrf>K8E59wkPk4L< zx1CDOw<_MuE$SPZzC|5Dah5lX;!JM_#Y)BESrKr1h0h;b?(=0{rdSk81HPV1{qJbK z8Qy~wZ&vuNiI2URFOZvp4b*&-I)h?~;-f1LR?u{@dWaS;;=L(U7t&+_e?vEV_$iK! z-7uWO^~cdV)737CJbj(Q4?Zk4jb-UtkB@k&I+zx_Ms=WgwZAvTtNex(r+8P8)SAR+Yi0OURMXEJo3xx!!w3%^3Fj+@c<$Fb*SxkpY^4Ge&xA~hhJCP`B zGvaoPYdjTki@M;S`G7IKN}YcY!n+FTlM!E6=Xuyn<|S2?>l%stF#V_+nc0o_fI`AP z#5>jBB#53WlnSglepVPMDUCU7} ztt;YT3h84J4^pRjXCdyVkmi<-deALGG;FDY;{Ehvhow+T}m>DTFJ=_*tbSe!nc&Q0%3%hMavDe1+O5AdY4Z`wWW zl(tG6rq$EX{)$|Ho9!C=u6@-$XCFh3|6A=%cDlXXj)FQB!(D{2{XFzZ5?eKZN@6WyphcCGyUk zi%d`h@IJj8stvb5b?|DjHoO0A^}o?(m>GyX_=lSVOTj>{~GB)K_G*`!_k_>m+8S*O0 zaHru}GL>Yw(+b|ZN;2GOR1h?kWVqALgjGp~JDr&eB*UFnu*+1E;Z7sdfT<+Io%V0_ zqe>X=P%Z-=tV;6RX&)w=O7h!jj53cZ;kU#57HDkhEHWAnIaAq$(EuA0dcA;Tx6@t* zNOn8zEOM1(x6^Leb|u;EG>$l`gxwCCy%LRZ+hMZ`cCJdY+i7Q@tR%agcH;sn$!@1n zIn`8>-A-fJgsCLEoyHk7m9X2n9jgx{zn%7A{;MRvorcBJRFdCLyD_JgwuL8+#r`>P^N{E8;B(TU;lHE=_w^JqA z?X+{WSCZXMD|ed6Zl^sM_A1G4r@hC3WVh2U$3vyhr(rS>{wH@v+{w!@;?OF{Z>QnP zHWlQz)6PFyL4G^!(hO9P-%hJzfaJH+eilf6JMA3-B)^?@Zn+Bb+i9;4koRkmf#kQ-{zZON0lyv26Z}#YWVh2^F_7$b+6~{TAiJIRV3MmKyPfvl29n**Rk$=2 zWVh2UUP`!Lj1 zkmF9f5n>hOxYN#EUO|pK?VRWpt74 zc{GyePUEIG738_o-Yb|-o;!`P#uf0~;gpA%O_n?De0&vTxzoyB0A#t-{&PUG+-cYv zqY7B=uof~`7>;Hl`v$9D>l{C3(ISSt>v*31kb`R%kC0wlkk#z1sa zL4G^!e7+Tj@)kM+yD_>2x-ymn4}u7^IgoHf_OJz370n|Z;H%AvW~RzEbd zk*$>Mhw+7vV zDQB^DBdW?-Y}Lg;7F*=DiOM0i_*9IQFNfBGj>2q6Ev_^F2%u=~EFf#Gx&R2Rbql-! zgxI=k68&+xs4f0{C>OOo49IHh#-Nn5+Tt%aQx3ItTR=Zk&T{Kq;N>E>`vFC6(btu; z+#f4(Va!Brc9EV%vvoO=jvjNLKiEVnK*L^L?G*}8|qok zdh4AJWW9A`A<9{AJ#rs;lag{uZ&o>0SsbyYP1;uHNn>6hsS zwF^%H-cFaO?dh|4M?W8#0B#NLNpDW4n_Y3=^on3sI$j@a9;~VpU^=QJgF5NqD0VU~ zXq$FP+na)5vNCB?WD3mrZ>NE|+U~~N^6yN4S3f{4weQ>~SAkOqy_AT>c(vvY&a z_AVXTN?U~DZd2_f)SDP<&#{B?&i(|9fG<%`=;ms+?W3-?huThNDKZN-a`gp*9aR+u zf(6OPs%x^`Y>HNKoq^~+Gdr1MRz%a2+l^7>Nhv)3Q<6)P^OLiZfk{8JCg~M-_PPO7(ToEBVVXp5#;gME(wagWlN6@yE@Js1`6R|7d<{;AawKoc~7-bA|WM?xys$LfsJDWXMcbCb>Wr zDu>8CyfqWT`%xj(6A{9@Fd@8!6~cQjA=DWW!aF7*)G!f3^${VI^b^86U?IGH5<=lW zA-wSv!n;`^)H)GD6%!%6sS?7wb0L%y6vBHap}(8~X~WxpAyhvRf)hXpl~9CG3q=V2 z03p;x5kh4YA=G*i!uxk2)O``cJAWb6fDwXkKnSG|h48LZ2(@2?P*p|5OWW|5Uu1S zR(B_#YHm~ZC0P#7V};Cd%K>YmR-k?wQCtV+D{o;*iRUm*)}rNO_G5+`_9KQ) zb`?W&SghP3M~&=ChKBY7hJEb&3=Qmi47KeFhP-{3AqRIBt#0i*4Ex!)8TPerF|@UB zGPJVG7+Tsl7@FGG3BtrKWr*!-493375ZPB4Lc2t+9QT}m2{oDqsmD+^t-62l^k0@k zRY@V#p)|Z)!?fz&$J+s!i}wRUs8cC~N|i!*YaoOwmqK`hAcS`aLU@ZHg!c$Sc#|N6 zN}93<)l(&Hc)uWR_0vGwQ2ta11yP0Y{y_-u)P>*wY0%`AcRUsLMWpe^O1K* ztMm^J2AK;FgAm*mLhx+}!Qml`GeU6A2%%1) z5DLc%!LKC*hmH_jYC>>T3Bj2r1gDY^yg@>6Aql}RB?M2K5IjFZa4iYJ&n5&XmJmEc zLU4x(!6zgHhm#OIU_x+>3BeC01P7K7yjDVRK?%VJCj`fr5Ij&qaDNHGKP3dmm=L^E zLU2h5!T%)$CzlYsYeMk(3BlJT1ZS5JJXk_--wDA-CIp9-5WH_faJ31+k0k^LmJqyd zLU6SS!8axZrturB1HxoiV zGa*zk6GG)nq0^8_oBzH;HB_Nf;;J4N<%*>Z1&W1Gl2`~uhlNmASg0Syyz=%?JXi?z zfrU^JSO~Q{g;4!f2=!uxP*zz8HD!fR1y~66afMJRRtU9sg;14O2z7RaP-$2QHFt$j zT~-M7dxcPeSO|rig-~o-2z7ylP~KSxg`b5`omdFRJx~HX_i}ZY09uq zY8l$62}7Gydd`-q!TbNr*6`Y@_x~T}-^{!Ge{=J9=F9U1`KvJEe-y_1pPWBB-y?rO zzIDD)zGmKFl>d(0*4(GL_i{^f&*vV`J&?O2SDL#%cLm1vpNkrS19E*(C#_?yMXo`v zT25zoXTL>tz}4Azv#(|sW*^Pom%SxhlD#%NDLV$W0Z+~L%l6E6$+pWj&eqN**-ZKq ze*LdcSEkF-Md_32L+R|aEWIJU5)}c@O^2id(mrXov}4*b-7BpIfBo)1IRF;g`SxBr z%NFC8`$Rk14zs6X6n;$U{_wT5l^F_QKYVj6S>6!QusK70#8g(7&d|d<&z!|6ka5U}! zmjkc~)Fm4$^9x)6o6RSv0`R(d(L8A$GI!!PZlSrxOf+Lq2jDbwyyG0R^ zd#MJnEPM&s03QzT4re+)0KU}!{QJ)t_|F;m&l&jt{uzj7;SGT2nZ5mJ7G3~=I)U)~ zAJi6v$N!*KAUypCH38w_KPU^rvwzV3AUyg9wE)qRe}g2_v+(2}ZM8sn@DIv?@Z2A? z4+xL_LG3~K!vlnTp|e~U>6w-wJWc}P;r=W<@&|PQ;fX(JUl2X;H}yey;Ey)E$DD=d z{UB6_n1#pvpavj3?FZHMqnUWv4@yCJ)(`3k!lQoBejq&Q2ekv?K|cto$Y$a>KL`o3 zXW}tGs3r(c`9W|p%)~=}5UL!^#4~WJ-=scpmK<5(G1C1oA3mQSx z26PTlebCuNH9^CPs)L3RRRf(xl!DGAY7ZJpR10(lQ5H0Ws3Yifq86aRM9n~_5j6n~ zBB}#Al?ZukXAUIl1UiMNIp}1fMxc|38iGzF+6Od%r~&8%qS~P2iSnTSL>M+X^Ee_4 z>PNI6=vbnCLB|la1szS)3e=aVCFm%krl3BKLJT*W*_$W^9Z6(BM-WAzUPK|NCy~aw zhZ6;$!-y292az9yGrPN2pCRB-_x?Ey%*<}|W(XB`%*?KY3NQR-9>O>ncrc-lm!vZf z;$>F=yD;MI;miXW9|U$L#7K5C^8iBME!&ClCE)(N>{?()!VF$I&fJes;m!KY4vgml z_hqaNY)^>#>Sks;LJv|fv#mQU9iXudHM;bTGg~vd4{ODX-hxG2(xR==*n%3>Rlw$q z=Kz}#`gm(Lvnk`%z$S#=-N43#nN7fb81ZIxW+Pqz_x;Sh=@Z7Fu^}}ow}HKQ{$gMQ zUTZ0^KDWXgFtZ*ndmgYZq5nP*`iQr6{#!r@q(k?eKq1{*z>9&9NcTGLIv_L>=t|$r zgh;w&TL7VwZrN?vF_K9gp%F6a*22sAnb1jx9f1%^x7O=GD5XOuA|#diG&Djg(U|!J z2(5H$;mz$#h^51+K&Yi#YYPx^>99Kxdg-te5Q6D21B7A%y(@u`Okl=cAka*p!WC2r z(R2uxRwb#XJ{pZsO*Hy=gIWpM1bQz3p_>k`0YW%|uJ%bKl+(51s;Go?0{usT&`yW& zkyMg+>YiwXc%spj*{Otjy7|WfA)i3x(>0aQPls;+A)r8)4!05t3UoC!Dj}gjSAU}t z8tPg{0wJQV72i%JRMg=GK**@WVL<4pTecVoA$91s3ng{<8W2+IunfCPTB_@y5n75y zmn*XpVhYSG!F*Cv-4u;bQ@2tU2sw3&y7K`&b@&(%g6a@&vMZseK-8FtDoIlHNoa(m zx<-7FmC#hTs5|)(RbU1$sw<(YKv#^Y60+*BD-gQsmUTxCVFkMEzm=q{x;h%6tZq@f z`mcnvxB|B1OVx#77}bNzBXb6s-na!qn|b6L0ren!TC z&$6rjm+Jq`Pw!19hMGEk4<~x-F~~Yaaub~>>u_g$^ZY>zpek*-FEs@ z{l6W_wq$McKJxCpfN||}lH2h%{`%yK&7Y%^=k`}pPmf_dD`F}Il+X1clD zj58z5VEWzP)$C`QoBAe)?t54CO|%ie`rnRTjuu1@M|VXPQ9*PS-qw$bhDIkveWUJC zr>Irb5O3^5{Nn#E+#Ie6-wj`d|NqhOzVMc?B%CJx|3UEoABp$)9l~Z|y)Ya4dYAr2 zZ`2>@xAn{V89h(mtt)k*zD7^b7wEI}K&t=OocDR& z=efC28mJ_N6v~ubB4oK4x)r4>cU7*iRQGmEC1JJqK08wyWJn6JB$g>Ot;kYHM8+aR zt0+Sm(kdA~zu)gHEEexS?|1EU&OZB`=j_k-clOys{B!(u{8{{7{Cd11el~tIo)b@x zKL7tK;_A3he0JPDJ|;daZV@+%W4TxElw0J6|1p^i(Et(Rw+a-9M;@`w9rL#8K7dJi!{e`UP2dsKHb$K7m=Nj7P&|` zA8C<`&_}Kv`CXU>JU4Mo0%$W^}Y?#O+7-^-CN^c|=R`2{X{Xft7pR@ZBh#4Rd`ys`x@A{l9ci^$eU z3tU8preq6TM1zfNfs1Hw&1`{-=xQk00vFLW`?3WtqTLj;1unvuhb|Mi=wq+f_6$Gq zyO9>Th<3owj$7@`pB=Zdh;-cQbisMR?K#K+x9~U*xIGOCZuJy<@~gmGe)i)@bll31 zk&at^C&-T5us0Iia+Hp`$n|vh!9)v1I41zIEn-w?|O~cuNu1qQEWc05%V_y#wjA zW#zIiLt8D3-cZM_zQAP1tzbVH-0J1N73sXy@gUiGEBb6ZZ*?iJJn;4ir1O?&`i*(+~wn%u(iM5Y(cHlPp zgg9_VzvUtTE*Ehk%6Ypln+M)L6FKl!uMFPm&3#Dd62R5VxfnSRm+F_CxHJT`3F4CL zJ}3w7&TMwz%3eqZu8#3Z4qWX>n;f`|h-i}oSGNzq<#$XDT$Tyx(g0j7ivxFbJJNwW z`Y96NQbM8TBqwfIj~s|gHB3(2=3B^D`@Y~mIdMlkD#?kfeZv#P)yoCB$&pL@Xac!h zB6TD=aYvURowzKP(I$w?cE&y>2QCCl18`fU1Gl*nIRJM%B*4{TQ^%4MH(Y^q;xgo` zO%Rt{cOub&8^Bs};Oe-wGysVjF1?r>xcW|;2H^ez>A=<3$OLdX z*@Q&rEkpLYp<-fFfh?Q#NcnVh%U_>czPJ{39e_7tS^Ht4-^ z-Zt+-I&XEYf#kfEk0YJ8j41EYz+0+ua^C8C7s+|cvZGyc-Zqa$4!m87bl!%0kj~qn z5A(p=?<1YJI;1ASTYi+V0_nijWlxd=S38*}2d)mEOb%T6CenecE2|_2uC~%7fUD0E zEz=3&a(fR&0bCSAI1;=?!KdW7)z|6dxNSa&blmFF3dwP+;|J4#+czN{w~R+?6SyTW z+TIhiMQKritv+BEAfc^Z*4vPdTV0SQIc@_)Nse1>KS_?;a2e8Zt0R<><5ou~CC9BU zmzW&4;Y&!zt&W6Aj$1|}cgbEd|pb&uObeAd}Np4K6utb+}}Lwp#MFNXKn+ zEYfkSgUyoTc61flf?KkE5{XV*dxi(v>Z9be)s+d;KwCT|4YVclN=tS!ZPQ8vZT0bR z+Ui&V>-0itw01gebw#1%wAFDt$!V*@WRugD;dwg#e?>U?|GodeeA!Euy&m>GW*W#>e*$pPt86&J9~E8tjtcFt(&c#9W;C4>{+uX&mKM7ZnpVsquF@r z-laR1ZdtnFf3E+x_tL-C|6BUE_5ZG!esOxy^n&U8r)Ny-^!3yA(^pJaPghO%n(F)i z_R}q=%ct$+KIY!Pb#mk6y2)22mrOo6Ie+pVX52q{a@^!KlS3vip6qSo{~yKre-E6@ z=(}?F;%^s!vG{|SBgco04;(*#yyy5yca#qyF>hV( zf}pdSdq;){cGcWFHor!idq+7CY3>~Z2&v}YvAGm!?j3ny?5eqUq}p`V+&hkLK$?3; z9feq5?M2)LY3?2A4b)Y0@2I0ItGRd7z_;SwdO5^X75mmgo`y94jyg7}nt#X9SzMa= zcjP-{HUExVvDy4P3PE^XV^@AKiDutX-i|c;j^R|K*>`OI%C6WqH)%H#ac?~-_Fc`s zqhR{g>^p|PAwXl)V9cK?j4&0kmlZz5%+Dyy*WyMI?S7M zDLSjUcho_7)!aL3lSei8j^T8qxpxe_lT>r>s4q@+rQfX$WQ%)~?QtZUc}KO=YUUjU z&{s3>81Thv<{h=oyPA2&=4E7nd6U8BB${_e9iLv!yCX4JTk&qT?@!_$evg(R&AMar zBcxe(47VZ8x+4QQyK2@Q^$oh3b!!i^u9|g6ol&8hb;l7NSIxSkjuo$F-BH`Gt66u{ zH->7~9kuPMnsvu8L7H_(S%)<1jylA%V%_>6@j_7XZoSW2A~A1G)d%0)J2Kl#SIxa+ z!*}e8dvld*P&=B^!kBkQ*$-*n9mD&Oc(-PC7rUByM~0qy)*aQ4t66spcOcEWWB3fx ztUESXWW~C*g!=fHcSmhZtmfTOqqu6`9ktD^ns-MAIJXt==45&Xv+g(|z^P{4QQM#@ z)=jper7`c0&Fx6@?x=5$)x10M`K7Do-EmZq=G`&ufHd!p`r1{^yJK?_(!4ur{8<<6 z6oDwOns>)$SEPA&q@lE{=G`&yC8Xlr6jz`2=tt@0h+n$?j={DcN+S1yf`>mKM=swK zy53={6O8l&Y=47v*lMds@37S%t9RJ)1+eQKwmKTM2ez6o_oj!oddu$!Z4YiK*LoDR zMe)t92ev5S)WcfT&KxqJ_FAN)R*pnEYIS61@2DLCpWacc?I69QR)?zg18RXs@2KTl zPTPZ8&TtrsPFihK>7BIN@X$MHiTv9h(y}Yns&~-JEl3Bg4(#s%tzOl=NJy)4BjkYE zn~`fQm3dXVeqimxk#Yo$pF@p!4@PsYMUF8 zj@sraNJs7H2S`V)4)E_Cwa1`9Eyo|u=7F>wav<$x$bqygkppSJfP}PqEqX3Tt7SrFYcIRY*szoQoV# zyBX3^tJ6*Nj#`~VqIcBNdeioxmNOhc;y~KhA)U10wMZwewzBq4T7m1mlXmo0GJv#X z@FEf&wDMjgpye9%Wub?(DxZdQ)asjF@2G9`@p9CT^nQR^-S=BaSgZ0iNQZ6EXU1Wx zt#CcC<-Es|=&a?ldDlB@wGpP@#xl@P&b_l%-y3>ZOSa5G(e}WW3?4<|z}lOT&f4ZS z2Z|9kudhq@z}@MuJ+-)qD*(u=Y-*vvzbj(pfut3({Gu zFW0@ZHee{dvo>fMowbeb>#S{v|9V(U5x{5fs12G3YDs)4iB4M4pL`(gFOg1KKJ2tT zq-EFbNCdPLR=ce9PTJ-^UR0Oa`!) z4A!AQE$T=%52W3Ibkgd~9AzLab+kBXb;_6Gq?LCgowRZ)64L6qaI4~|ZN7~()Jpgr z(oiex6%D3WZ_@|L1}KGd~izgPC!Fl4;idzuIL+DXNm$fpHWMQw55&5V#2x z0>Q+q!~h~M8-}fiV8wbKe*$NQ?Z(Dp)?yWy`;|E?SvNAvaw@D212pfj{*l&x(X$<|JmmOp9wczFc-#(9K9kjVy?HU{M1tI>Sv zF`*H`9y6i%7ixh%43B(i^oTg<#*o@?_xg7Bd$_)ZN}lz5xC3c{bPvYW(|7u=F&$?z znHCk|5f~X6<@=4scZKg>nAq4Go10ogJY;_GPL^zMY+`J0Y;9=lK!)xS=o`r@np4Bm_<|2k|l8nS5% zIdJ?M8cge=7#97@+y3$(=MT32OO;uMKg%3oZ27xS->mdsTm4KNX3XmMn&0hdR{oNi z35xu0$p$9SEc=~pd^5_j&`o}CdcS3h*%!@^U^80;&Ke*3~@{jkKIVzj#&yM346JuH@@`E8q ze)pU7OM{J^Wu!CZ;*3Gf$Qi?$WktWG;7kB!lxO6RrKWd;uP0Ol%`O)q%^9zMUs%ro zobhi){{D*ZRzBMXe|N(lEb&{(`CBYzUpjN=AFh~Xged|<9KVAaQ;OPm)A@Gx2MhC< zIT+|Ovp3r=*?*VRztWoJ36$=?QjwWC2d*D zm}Mn1MZ&*MkeM?x9?jU*?~`suGZOR^d|u5s$p$tyma`t<-&1&&19d=KSc9VMo$z%%l08q4ft^Nsv|FT$@9$ai+$ z{igaGLFg4|7Ovk+-OF>eC(YaUyXMcS@*Vbfjc+@KHr5V~_WGbcSt5`Y8Rh2}=p6_q z9Qm7%5@bU&V?ztF%*-8hmXeiZS!I<8Sbk$M7)-vA-`Rj@?QmFr1tF%u?-}e{hV=;W zj12g;kN;f;nym!omTztu=ptEXES6tch$;McUix}@MEZt*)5ZNuPp8#pdpmsuEO=RP z{6a!Z>!cWo_syek0{cV4eLFZSGtFd;8P+q2@ynF^7K9nyKPWuufg!$;;Cp%4H{BU; ze_zZ-d#?T=3_s*NvXQZgzLTXR*+k#c!T2{5f`EPx1-SDsUZkO_!5^r7H?-M>?>DHk zbDXc&>QL`sFu$cPWnz#{x0@zCef-;L^pB*c&&?tYrt$2se24c-(lZr_nJa#XAlb>< z+|Fqh4zT9&y*dD0@iXmTUGT#l{@DAuC*~glXO&gJVEOfh{#8c&#?eFN+YXBY^IX&^ z-_$V*=((sXe^ZC*_IRk0$pZc}0Ta;fJYa$?gEfd?iC4sNunc3iF{`5mP@kbw{}+F$ zLPR{uirEU2CiPD5#&WyY@tdxnXsOgUA@_r%7indRHqdLGb-;Tr$`J1S6`46it4LKax>z=4{GWpmXq*k#GEEkQX zA3C$Xf3e?i!_)+Qfkj)pX_xIOezdY^wnv8W){)V~%&I8&ppdgSd2mHq))x!x-v;N> zDHRRb22BLcn%-)S3K})f(XXa)l*_Fshf6a@Q@;XhwX6EES8ETxyX$IQ_u%0&7jFAg z8xP2XsYFD9VFe{Rzt$nIcC}Qiw@gxQr?hUZZ4lq^DQae9_Lj26o0vzp!lWoBRfuhd z4qUL%2Ptu_#gEx_*Q}s_6j@JwBvFMs)7w}py7ahg@v=y|wm~FSE{z9{&TSnYV*}#K zmPaY7g|hR_>MlN7UKe!KVU@pM*YLh1(QP8PB;T=LY?D5`S>T0k9ctzCruAIG7FXKv zen7Ii*#q-YBZcCgKB0L7^5t}57hnY0> zaC-H7wq_~xISv}MFYM%GJ!2xVic^m&mq>)8^ChuYE3VwL&qn#yG+?8aKe?Sb85H9C%o8N$}bzvr-tN$F=hqnECMCw@aWK1A4-nIw^iT7O+l1}agX%D{7cLpyO%@j@K>_{cHN{3J7)GImYTCQJwFo@`S1 zD`%mT6n{6@f(Vcp0i>${(WM2*QMB24_nxxC#D!QH^wmcf?=_RWh9+Wr-drY2c7gXUY4d_M?u&~CfNqfbVjHjVF*`T}!3OR5llCTGJ%3w==jh16NO z#h=5}RQ-6|4K{HSx>O`CKE2EJS);dspX-{8A5|_214s9^#_s%QXOndnn=Wv_8Zc61 zdQWOhTG(-K_x|~rhPzF#TE2MRY9+QCR}d5Nxz*mez9+Y7=b9R}i3E>%x17bD*aZa3 zPEY2oK3LjeVO2=cF|S$dxtDFpjbWLU=~Fw`-s}crj0fpL0fdxL~169+#@UFA6sN=zw+zFO8-r+9=tK z3WY2hEnMV!VN<9bWhGU%@)aB%0~r(UuM6HPS4QETUY z=Y1z?_+RgH)(Px!&pe;AuwZ3|^U1aV_Z7Tp_V|Z8J4AYgq%-Q9zi!o7+5N&hPv%DL z;cYMHwRgQDzpCp%T<9glMN}tk>wSTg;5t7RtO#afUl&n(gt}TCh!)x7)ONY-?y|`dcSlM41efMiP z7-K9*=MF??vApUDZ{PPKh6-a|rn}2T&*k^UahcuR^m4owJ#_UzCOjRM89H#mLKo*` z4%V{_t?e|+jHRhHEMpe4?%TEZu_D2@+IRjTTQ2SXc@F#)^tBwP3?f81C~3O89H#mLKkO;b>DJh(b+y_{$?9x>o$)k z8s#k13)^Bzl`l?MZwV*1Ql(4v;OJBn3b-^Q1c0OaV{+6lDIS*5ycclq(YvRsmnC)? z5ld7#RC(7vI9C3gz%dTQhUJf16?X5wxb>OLr@TUM%EMjCG}OJDE?*5#g+=~NN3E6^|e}21hY<$dSDr?8c84^-BkM89bX znd-8yuY{q)oGQdTLkBKc=mR5pUnf4Vtw}ku$vQCQtfo`3-D{Ml&e=}a$GB#m9L!5aCA6TM$GEu3xaTuH)O05u<|_NK6=|#Am!~R`S(}6+)S3S#q;+Kj?3)+5aW8c z{jhG#@TMy7$)*MQ_2i^m(h@#0r^i^O`8oCV`pc?I95iB8Ikn8hPph^( z5sWc23Jfbmr*Y|r{AKr5SC&+6KC8aaSRheYIPrjvzEq#4Re@%{*X&-!Ux5PYpl9g7 z1q*#(C~V6I3R98xs}EaH5|ZiNEr#8@yX>QDuV!+cL#5=mJf=!%?t`Nfd!HvG(lt6B zU9mjZsqDjI>b)|bBWZOxtES3M+7}*6mOrwtqqDl8G%sL}%+uPqV8ds6;|m&%)mJW6 zO7we-(!+hIbCBlDt`JK9az)R!(eG(IsrbYd=Ai+VzQbHYvLX-_kj@Y!smBT{h$1>2 zA1!^`s_TO#oc2BNNUh!%%2&l!R+zin{8vb39mA#yG0o6{3l@5}iDFFC)eHSxk@Cm5 z1~T&`H4Xh;0LmrmssalI>r!)TCo2EDRd93!IG8DepLrL29<&BZHfPq1_MC{wPm3W? z$R%ZiGEq8Q-6IcdwXQghJdV`v+`i69dlUPiab=^hZCCf~*e9{Mh%hh3JZOX1%a>Yf zmI`F0m-}NoE=Y_DSB1P3uQbn1gvt^o6c|>B&JKgkd7tk0cOAcKv!uM_N+Q0TvwcTB zXX~ck)PzTDF7C5P{Aw4p9q{d7x}FCYEOZjBtjSZpi1x7GlgByKGs33%cFZ~PNHeKJ zpII!_gszBluV^%ufumE96h=R5E%9n^*y1x9REzUfYL;2l{;5w^yUr-qaRqMvRWSeb zF)*y)HR!d8m{nCq`Mi_bablOB`z-g>p5i|#w*8cL)cvP&DfJ$I>@Po&6;u!;`l3i% zfINT(bdrMEC)rzpvm_HzJ9_}>E76x&Nz5dE0?gSBf%)v&>?cUo?4rbnB=JA?899p( zKnNfN5CRARgaASSA%GA-2>h24n9pp*6y2|J;?_{?(d(BYr^HTwTDgN?%i^P?eyOe( z_Zs~uI;#MrR8aEac~aKXV+~lzL%KFH0ym3z7amEPFZ`9wRY~!9a|u7Bl(XMgGOWL4 z$U$OjX7A9r?OGXmJ0VAfJL@Da%-<}x%8CzCdNaIXW99uddXH*lrK<0*$H$treqAlY zpWpR#GLS9O{Uk4>RHS2*nOSykLQ83+YtpU#7Kd?k+v;}Bot!8(*ErYftUQoX|D>yr z;?!^u`anx>)I!^IYp;(XHL{#JC#jT@Y`=xg+>lc9tKonbccz(o3sQ2}Vx8}a`(RrYdQ)=V0DG>RV)_2us}p(KGRvhy+jVbpLP`&* z*+aeN0yiEum&esUW0HQP+J589dH=VSD+)9$+}PSV7)o@!t1muq@J)BZUk`L#{wrgYu3Nstn` zspzxragXBg14_m(YQPZ{7mA69YyjnGZ~YW6x2c1Q`Xf0PI*!F3a|K}vAFWvq}A zT&E8KQiAKdz(Y!KP53xS39cm@3n{@h=(0dcaP68HNC~caj~P;eZRL_=wj#ha_JBHg zxF!!q{U6Xq0iS?jpdaV~+JXDPEua>t0Zsr%fCJ!o{ahduNCo172p|aX0^9(5U>RTl zUV{7(0tf+w073vEfDk|kAOsKs2myouLI5G~UqArI%*=#-w_SiC-`wG!mbdvB@})d4 zLw0ZAVaO*>xTj?Y7ekiXa!yMjvGVkNsMNu0eE90PFTzaWW`C?W(90tf+w073vEfDk|k zAOsKsf1Uu;qq&kLTX8L$rN;o-@2lNE$w#Y8K7t|CdYCKPvs@ z>#B5Dk<*mAmU;P?wgl2x^;?xWmwL<8(|H*E|8>!on5aDuju$3JETJaui*D{zU8Od8 z>`q|nFi*fd>TO2;iS0etEgJE4;}U(#UR6-NJm z?Zb!Jm$TMAba$?9x$5mAa93Ag!BauLoENO-vX|P1H5vW?$$K{*vXGj1@!|QJH>nXf zj(n~qY~FZaQ>?_qD{Wa}hfYTS|0F5AdU0NSicg|HDm%W%U2U&FEzG|HDm8 zWc2^T&Cg@>|HDmFWAy*S&DLV{|HDmMV)XyR&D~-2|HDmTK|}rja5Fie{(rdn4AA_4 zG%yO~X#rpY7$q)bGbN1RrLp%})?nn(?MxXcwclm*)2ZQR zOrRoAY)Z;%s_GPVEe$OSN}S33e1gY?$w^EUW#N9yoS^LT}q^HK8P_S830a;#sjZfxx2 zb4^L0Hm>ct7Fl57M-L_?fUBs4MbRTAv=byu^eMr{!PfqEYW7yvv1+c$s%G|fo~kaI z-toR};r<$``pe>i?KS;Wf?`z-y@Dy~{@zv++R94GN%9h*QS@2fgwPx)c786_c0oSj z>OspaVx3}*Xfd`f@s?q+fzhr;5w2ErOaBmiBd>^9w|Kg%+O#)GsW$8oQi4^w9V=BX zX#jQ(H7wF_6zhLEQE+{S--sTEoz?zlh$<=1$wo7Wc*#?XT0VxE?yCtK5An-n(43xk%n{g?27r^;z!~1kMqor8oW9*IjfZ{-@a}xF*Cj zQ36#iHUoAh`rL_Xs;Fzui74&lMSl?;hr>^C0xE8OHo*PuRm6hj+rIFQEkESY>nLJK zuX`!N8>WL3);VT8lc8Df9?e@J_~nzD+rzDKH8jWy0Hd^U^gs)}W-$GPL>F+5>L4T4`?ZDL|7!M~WJh(bRBJ7F)b7!idrlme7 zrox6#4>7-6%`g@o%{7Fh%2Cf+a+39*y998xN26i2RIWtvK*HD6T z{*64F?j-}%%QD44tMt*(7z)kK?H!xG&oh*i`pKe!VwB!~Zn9*@IqPOhuXO#+i29=5 z$xA0w>q-w@Z_482eRMvGDSSbj_rq$F(&en0^GnO;d3`J#2zOjs58$*ohv{m4`~ z-ci-lB+NM4n`RqsYZ&NnpyC&88l`Hd8sg~UN}&V>MlDy@G72|V4zmvMF`-x)f)8r7 zUqLmad&apf^EPzQP_tqq-uWD@=98A}AinLuG=I!9(V-)T0>}BceVjbdV6zJ%! z9uu<6(|6Xj-kKqr6jSR+ALT$b$4Ec>r~uC>i-_PbRY&J#KFUS`kye)RG#~XCZVmnS%>g*l0JQc;e!%g>m;!lOn zUrEegrsZFF37lq{;t4K{0Tnu_Q7lyHqr$L@CCrtn5`_X{ol^!mmJ`*xKyhcp{{2g+ zjqg|9&=LsAQwUPAi;iwBnsU)|zY`{$&QzbXQLtu!sML!qPkrdhw%68{D)xoWx9f^O zCP9K87Y14wXqb))N)qxCkx^k`tK*;-ROpwK;t4KqF&#$?xctHg*d-6<7)LcNEwEhG zm{S~Yl62GkzPK9q^S>WRcE>bZaM`o&&4hDANf;q=gNnKS-ZBB9Ax4ndf+qnC3 zKzWvBkdy#hRnr-T#T3A{D0qC#M7X)JF|-D?a4c2UgeK`qEL*hUTuauH$LFGQ4KBnD z4lR1Z1RqCmsfp=0qN!5ac-Z9!<{U?ghVmTaC^5#Jwm23eGs*%azNl0Jh(zt&>H_Og zk7L-xYorjtMOOkjgxR>>Drp(=@z+_jG*sal`tx5HY8gy;p6#lGk0TLWFoFQ)#}q3n z|9c+TH3VRzsDWZ(Ea;8G(7~l0QCP+T;t?ovMn(qi`N;Wm2TsWPxShT~lrEvaWn3pV znvY(qE_cgFD!_O3>QL4cesF~bJQN&nZ~=i%*h%~UW5Iy}Po{^0HG08$;D(O`_5X_l zOHsfiFa`_(Z-5uTQ=koa05k&EfvdoIpc<$I%79{EH?RZP3Ty(_0c(LcAQA`x`~gp3 z1>gkO0Oo)p=mqja2p|Ly0tf+w073vEfDk|kAOsKs2myq^|4jlc%uGxI;?S0#%-D)C zwxZCMPlT}*hPJ#yjO{#V%Ol9x3P4+Ke#Vv$+H&ziTTUKm%fSt80WN4u;)J&B9MF~s zKwCBvv}I*yY>Cj8z{c3JLR&lm+T!rg7K?+nELdoZVS%>H7-)-T#xN7WEi<0LsQD>C14t6bNyfxu zq&Ctib`f?1AcXjUxbgpH5+kuf2p|Ly0tf+w073vEfDk|k%n*QvwF_z_D`~wdU$xP7 z#I{^P{P~)wIM%GDggZ{#&Q#N?b3Mc%CAH`!+c$_@xp=$u&>qu``EJeEyc5N@#JsOc zEw$Lfb~l|2DH+s7aGqg0dc#_&Trn|X zSw^aj%=Wq7;Tv*GTC*;+k8xLOCU2gQdrrc?*O57bhwiOlY{5&2FAG5!= zJInc^Jn_l6p9rLcGc7F{-?{$Ni^s<^+VbrB@OD-^EIg`pf(5)2LeB!4!jMwr;6Wjt zmcYAE#e2=euYq)zP+d38;H+ZHZ2q}Hc z*qnWs#XR;6=ZE1TQCa7Kp@!U=PKv7x(XR{1bT0--{ngAeEg1-(x{R1CQ%tVwcSJGg}&r*k4L0Mimr?~ z0tcH;=8NVjX*NIEw&vm+*XH20G?f8@qcroSWbOMssTPbm0@s)cBARad&K<72yQ@yL z%9HzfWq|t!p{iSZou3ZTlm!`c1g>Lb=I_t-Ozc|qY(J*)3_8eXGw(zIKL_zllio4m zZR(6U0_o=qUKK3bIndKEU$ALi#a-#8*`y_FO`c=MdnD~U*g6?=1l9y)=A;FUj3uuK z=!ie&C?)=N4YTi5U0_TKcQQFFb~9s+K>R_#!~ejkvsBZ-8rjJa)QOmhZ6{uFd-*(D z6|VQ;+#1Fl0k{#wj5z{uBexlI1mJvP%n^VaUdxyx05@WkF-HJy5F}%c0NkiP#vB2- zVQP#y0&v5h7;^;R#`7@d2*3@9VaySL8&SfTBLFufgE2<{ZcqbbjsVQZbdIXO$&Pb;Pq&?Ul91kEGp6v=iY3)&J`tTjYs1F$YdHCV``#x!=#{~z`zMurfI*6|(t+f2+6Ts_#c_v** zb99__UZ?WL0#+#;82oCMk;YAjx7#h)qP=xr8(u(c8&L*t6NU|!PbTbmo^xO7krWnG z3nC`7D=k55+zPM8!+NOZVSDanXogsw*)mmE)PVU)KnJZh8^SPe6Q789JluBV)>)3&i zEyWIQaz{WlW>9Uvw`fn~ke28(ay)mc!56LeM1j8ew*@lRw-b)%4IRqjsfGIg38Ygf zpcCxtZ-e^wfn3rTAOt*w{15^N0fYcT03m=7KnNfN5CRARgaASSA@Gk9fQE{|)y*0G z|8UI&jQ)SP`a7flAFlS!=>LbSyEFR#;cD)T{(rc7JEQ*}uHA&u{}0y&!VadS5-A-8 zjDxfPUjxs;F#x~%{$Co90M7Ye4UP?<0*-(sU<51yQ~`PL+W^7<4?qMkq)E~T(g5i> zsgv}Obca+&x^#WLhsFv*ZazF#0CMx9vHXym7mwwG+&pM3FXZOIVtF7p z7n+3|!$cr(VYwhDCl1RAo#td_;Q$ZgIj{iagw&}kf&33B4lQ2)OY zX&nj}20nqS{`CVrKs#^^xDA{H*ZwO8%E7Mz?gG-mSpkUv9bEIz7hLbp9WVy005xDK zAO$D@A^ffFXs^($f+v#gIbi`O}gh$&mBV64R0(&X9t5^0Y*YF{A)qbXuZC7?K|? zJT37;49SO{H!bmk49SZYn3i~chU7u>O-n2cpqe=o7dOMniQ}4<%$$tF92^WM z2M%CJb~I^PV%Zszh$c=;EE_|zp;@OTmcWp#X#BLq;uzlH8O;Gqcor7MVLS#iEt#1a zr*UXd{8s;efdWRsb^lAi+WrLC3UD2)`&Ru`kEimu2kB zF!mQP_N5v7-~@ee?fm)BmLSR4mtgEOz5sw{d;tK>_yPc)@dW@h;|l<2#uotaj4uG7 z8D9XvGrjtBey^ZAOsKs z2myouLI5Fv5I_hZ1P}rU0ffLmOaQ6>|HBD@+#Vr-5I_hZ1P}rU0fYcT03m=7KnNfN z5CZ=T1d#gw{{m#>76<`^073vEfDk|kAOsKs2myouLI5Fv5cr1)K=c2ZNMBIkAM!&8 zAOsKs2myouLI5Fv5I_hZ1P}rU0ffN+Q35GzQLHFtE8a8_W+pT`iB{I+DPKf;*zd{X z9O@Zi(|kMToOq;})S=HT7HUFQ#JNA`zBqu&$jHDwA31;SzzJC&x6}8B(k1k_jO)Zk z^U-V7(YQG|repoZ8`+oaqd9 zvl!>i?|FAS_YJ3e%I$M;9m#lgXC;gKU4xi16#1c0;$DAFRNajjj)R>;hpD2U7fvyK zD6`t>)-{-bT7C(IS5Z<{)lgSbMoZmbj#7f^|0I+e3h*PfvX8I>#D#38gb}d{mkXpq$uv|2je(22l{>6U74O0`S2o#%=vYM(oMO{ln zi-HnoGC!Z-aba>26Gd6L-!eHSN%o>fmP+Q@u`kUTn&hfn4wT-ml*J#ncHQT?EveXa zdtda)1)P;CMAEs9*WP{^DeydA;pKdkJh(mejg%bgm#Z5aJNaBw5~z)9d#*(mSoqO{ zNeL2xw6G|8q=a^Ygo!>S*f`kQ-%icm$~sofRaw={-p*6iMbkUp*Dc&%LsfrST(G^S zpGr`ys-agfMcv=qNO;wqn7rQo;1(Z8Ei2tv z)R;#z?vsOeC7;Z?4}XfNlJcBvG;@fTJhiChW0*<)+QN5VocyloB?zR&^4YDBWZfA{ zu(Yts-M8Ys%VnF3a}lI4zBqy@-PwAp$0p0dKkg;*K%)khcaHIuyK;M~Q|q_Z;L^-mF1)|wMhv+XC= zJm;!hFsP*)C}!0g+tT~FPQixh;DmLK8P8;B*1JdZRtSFiq~>;UfHS9yHO2bnyt_st zT#t=bx*y?d+4i>(RZ{wU3R2LYq*OZs#1wouIpL{tQHdw7q;#p!iiUc+2UfeR{W|!F z=}X>CZiCIrGWfg2FLxFG38orqTIzFRDs1@l5c9h=4vn=_9EJDB8?G`}?dren-zJ<_ z_~fhTQJKVDTeZ)C5A^Qs5qu%f0psnxn+#5CbYD`DN=@sxqTv7ie6Ozb_b_Gnla#g$ zKW(S$O>tD&6PV)~iqRkXl{^o97VC{^GH906Gz}We8*#hEmae9*e(T)anJTGkC_y>@ zMxIUgk^$;vnPQ+-`eRpzM#$fVYNx=a#qdxrRDRyJ{AsyJ1#8=prVcB)~VrsWU3tR zsOo7FW*qHJvkkX34D>fp@ryQ%QngbJaddH|Pyz#^maA(Sg&Qk}SqJ!-P^=8W2esO- zpqkM=>7GygsnGc=iTTU4{0lFE(@ax5vD1Z4Y7`4q`lxW> zt;gm9Y8@{l?;V{OBl0EVFYrU7gwJ8(3Nektu0mT3!QJ* z6@N^E1U)Vcv@p;x9Tk)$7WhRaos&wgxkS%!m=eHZj#>Z@? zvUa>a`evE%KxOSo^qZEDsV@8a%DKi-O-l!T(8!>I2IBne!89)YkiYD{>dKPJ&1cmY8Ve)}3nw1X(UjO_kEdQ;!u^5Jhx4K3e*=Ro4egIPH7jky^bkl&^}dtT1=C z`P}13(NLaa93{rM(-y~KWJXzl#21xH0FkJjTU}s1>TwL4c#RYyxadkChcFx0TO}<+ zKK?q3mWC=^Lx27YLoI^|&$C^1@Np!f@hAfL5|Cm=<$uq!!(emXr~Ca~$FJHfDKEK_ zh%e`C-%-!mx~Vrc;SrmQJA4#1P%QM&U|$*vL-&oOqp*RIkx{-8C^B4QK!ES+)uF5@ z{7iTh9tw{4ibmtkwTYNjRYv)|liG1&m!JDA_tl=_KPk5Tly=npr*bLv9{+Q}fdWrv zM1<@OHA}EU^?!EMXB4T2y^dJQmcbfCu*56kI9P@;+nCkS0^q5ie%NEv^;C0SYJ>0< zg0yj~%(bed-n@aJg#Ba*)^%H3S!#G&RWnZS+I&DY4n8W3da9bbs-~K<^4#hG95-kq zJ4Jc+*KRwO;&;eRd}C6hT|)21NVn{TuQp}xy?uJ0<%QzzT?Dg^&4f25udXf87F|ZT z6R>;o)4Sg0&B|aQ0M-0cg~0lu6|)s4P3oQAjpcT)<2PMD(NeuT+x|W80{pPY3S!nk zjda187FCmKXmYM|FT>z8#4h@;6cC@*l#ra?lr8 zw6&Xd*`DG@D~o1(WC(8^8BNTrigFJMIeU`_SEOZqvB3Uqa4wxv(U5J>MBuFHt=6cZ zQS%)AY8pqm+=_CzG;=idE3j6(svmo`_Tam_uGV!A9xij?wm-G;fIOH`L=+fSP@-Ym z4}MD3pPZOfA+WADO$RPm=!2BF*5b$Px@%U@KZ>lUK9Z=yo#}0?6afb+uWNW;lIS*(TaxeCFSbb^-YoD! zw+^-PdDD8XV2dklct0Rn-Ryz+sF6Z(PoL1d0r_$|G4kDoklZDLZ{v#S*Rft*+%Jo6 zfiYwQ=};g#unkr};rPjkNnHWf+aNmWQgFdSA6#_m#`V={;bA6CJ)BC5eU5_$ z?F&0OSQu*v3)A4hsye@-JoC zs_yAmWB+w^CZ^Hg@WH7lk(URs9Sz>NN+rGHzNr&M3U3vKt!*9;_8&4{Je+VP>YUmw z@(C}tmh#Jn^Qj@ZU<_G7I*UMwfo&=I3CB-P+`nmi_|z3(4Q`q)T(r;!6_On`avUsg z$^PmqoML6_{WM}q{A}i8%2WNOV>N>7v2UnyF;qCZi>xpWp3%)kq}Wv$g<6OBecidT zw)_q0pXL>}3EQg+y;lD?|8h#?>ng=2mTs9qBa6wf=Yr9cf#>*!cH*Mqg*f!_kz<_s zNoF)vGdM8!lStgYqw=&dM$R9Hyq~$K!6WiIdQ!B60EQU9Qg>y$$?a*JS*_8a4)w?rn|T z`OnTK>nt{1;C?k=q{#H1)R?reop}Zqv>+ zHEa_J9`kNFi#xFk2$r3m%v*i1w8O%xkfLK=v)FSl+mai@GAq-kcCNkI4aN`;(uK-D zG;D4ECmcUH(WydUjd_= zXP(blSg+L_`_{m8~6#}a{({$j1g)X_1^m_09f@@-IQ=fQsPiSV@oIKPO zUg1$@7L%b8)KH(yO_j}qeJb4ZqGO^@pjz&=jF%kd8eF#1r_^9`_ZgPb?eUoG9Y+q_ z(`A2BVw1hD<~B)F&9Uy<#}DIr-PuZ?(vG!%vb7PC%NDJh(b+y_{$?9x>o$)k8s#k13)^Bzl`l?MZwV*1f)!9bI6BpY0xr!60pRHVm>l&> ziic%1?*-g@^zP~EWr6DX-9*@^F_j z4R!CP%U8owVUa#BT*AWY)ySO_7p^P}*;IjgxRCQM^qz$Q>10E6U@OBv;rPkPoGJuX z%BJbS1q*#(B=76Q=e0E{M>bgprkvGuDzG~Mg%#)+}QJgBZ)E15or^<*~ zoqRzM?(v3b%}#UtSYCLnfPhdmM4NSWJZBuh3LRm zqkqEjlM|!^R_CVazy%9^U?^_NxzDP!f{q-7SXQySwb8Yp-T+jw zY3_rg6MLU0BhocG9$m3K*QxBoV(PszpCf5?Ijg41PTCh9OO`*fuA{TMpfoRFkId8B zxM0I)dgBWkjn!8!RZ8@GjMBq>sB@6!%&rhh|8hmow$bltJgNA^73QG;DUwTZK4>{boD|%SET$gu7S)vNlin47l3k!x~jlJ z!MfDk+KI~lZWSCI0S;!$;Ah?ip9ighlFgYlqdg}g^3!4n6mm(~piGnwSNF&RTdgaO zBab7sJGZZM(%!^=Xk6JSY}?g6JN8LzE+WiJF%Q}x_VT6Hnxz6+>E-^|jtdf_!c`$J z#VgHo6Fa~dGNHh*LUds3?myx9$qCvH_;xT&2QFB17y$rQ-(}$F6eNYw&ss~o+8eg` zj0V->e3hDI7PWuslhv*>igjFpn|})7Uvq+C1usOyHYNPA@61sBABXKikxsDt5cybd z5aRLjpp&1)p7|mA^dDc0oJ9!yGXzqF1ZMJdbic-lTSKu&uV0Fs5!SF&U)u4S|I7$Ez7wY}WGe#!YJvk#$0di@GX!^cz2qN!~yN^p5P zW3aJYYif0rwQH=pXu9gU`Ilbuwu;C4_Z)bE3pNp~Dk%BzJSpqxu?DQ9cjtr-TsY%( zSEFucUx!g^R}32EQOL`BWzA*Voc+F%Vf`&b4iZ~4dxyqt*UHG-2{|gm9x!x1=@eLi-qZrDpPm3O*Twx(LoQOh<1ROVISm+XHJjytfQ1d!JkPTrTKF zuJzI~7<$ZV(Mz^(5V>;kcIlx#rW^C!ny+~$if@T|UzJ*Fv4!n!I?VOh)Wv+?Y||Gm z+UPg8>!K?$QF|U7FHDYDLQUKk-Q20VN^SDkoxs##o`8AO+tlVEc{uv@G3u8F?sDV( z`Y*jpy0)FVFlmKqeDszb67AVG{`dsrH^Un?R^DHu_o!A@s`~zVe5_gP*VQuo`CU&Z z1KA?oPrffoNiM{#)sDS^`&vV%XO78eeigs6LWZwnRf{D498`nk1L@xeB{R;nv}Ane z`cE$&AJ1sZv+Kj#S?#d!sMZM<@JD%SaVJQ_Y`e{Xk| z^F?{$lW{*7dSKfR?>F0(f(thKrF?XUf~2j<;<8nn4tsAr{n$OV#(=()a#by8cd!F0 zl~R#vAr5tQfTOC+1*mPU8L&AE4mE1JwOT_%LsLm>ZsRKm%LKVxHhz9FK}WB|$$87R z?LFnO=c@XhA+?0HJsgpOQC?TA+*W*eI(Kg#~$rCO@_L!vJhfnJZ1O*GQ8xH=4_W973ht22a*(?Q=bFSSsrP|JEbX z!}hn^21Z^jprT&Aec72>`;__m*z(sMF8v3-7_U9Fv+s-_8};4#8qP$m|0tSbvuSF= zmRo;G>0Yk2vqtBKU*E3-Tmrs7@zj{*{0|ZG~u2SbM2RcdNkElq0VXe5vk;i zRU=0n{lXgC3SV2C4OLw%61ZZ1Zu{iEt*mB;1M-eNT6>0xmX&{?_(h+lMuR(6V`frWGF=Rrx7ZDWkl-VM^j{mCLj3xC&HHMf5J9ZROe=!Bd0Na0k{_PiTYFS z+G`Vx=`>t&1!q?}(beZgkW^(6}%C^q-=&V=(HU)Q;^>`8qD4tt|jjC&+| zlKsc=KL0FD;U8$iJwyMDZK|lujpjWTF`eaK)p{4&*$eph-)VT~`<|sO_+|Xg<{RgT z8{R%fvr0PyY1%H_4#_z>_@M|%-}MEnlNW6)ZShd6N~;3?lV}Rf&;)y+Md*=(2ZeZ! z=SDYa+L^CD+xN+U-{Lbt{h8eTzOOAVtz{f@r3v*wQ|34d^;oM&Z&|mi_Dcu5?NjX^ zG#c2KB(HvExQ;?qj@fkjB#AHZ%O|!)ZqLpiS5#f!cB;b|J^u-wJZN?qT{Xt@Qrn^#6bE|NnPJ{{OC= z{fqQdtpvZ(wBn(j-Y}q56DKnG+~?|a+kz1w5^=6ihQTf^NW+19b(xWO}(x!N)~ zlR`~J0~$>+;pt}U^p;1>%xrSy{=DO^p*tkWhocjG^Hc7xoxk{UB1$jBkrra;7q>Vf zfLhoZ*&bBVt-#-E8kCsL>iasqJxWC4hRzx70rcK&><`l>6d&|DZ@7`URq>f#)@iN$ zp+2skZP_X&z-yUFtgUyjt!F@F2#p?Nq8#dP5#(YTuW8~C9!l}Hbf!lJ_&BL*nA$CO zGXlp|QhtZ?uawrHFG;CZV7mb0eSezg$BfO{hgr;H-*A2y9uk#x9vEuKt?8r)d_KD< z!^v*F>7U}MsyZj0_<@k_!<%Z_pKKaFrEnG(z6AG5_u{)Z4gIvP4;y=pK3faCdsBpS zd0sYf*Zy%sZi>uHskm(>l945!ovz7+(0_iX`iFUfEeX>+xv7KnC{dQhiD^e*8F%&?@cxsIh)Q&Un=R#%(b8~@<|LTS~x&Q!?@e0$!1d1Yux zOrX`(GF{)pQz(Aqh@;${>W7le^8?;&5%g6=wN|a-UYJpxtGL7eDUVkK3I7K5Hv;cf zJm>Z%P%F2-_{46VDZ5lLoYcrkp6)AW4%m8R2jNtN0D3Hw<8qwLdy=NnIR zPO5_Bg=@?N5ly#!=MGoi-Bl-A<;nfLGQj3nb4{RcHyU&*#6%LE2 z{ROb!4VZrXSo)dl!oYweiPUX;rf=}=`(<|Rtn3u#=ZFnPm0R?C%uB}n17`CR|eXD8$JH> zM9IG})c%`K;9NKJVAHyayV6UuNlVt6JjaapNZNO> zb^Zyi%9^Tko8j|fjFeb>^~J_Rc26YuE86upT`>0KjKZ^&wk6r;PB9&=RuF1*m2)#` zcV1aqzAj8ngD*vVwS6~g^m?4ELB-Rbxc-&*9gxP)?RtMPc|c3G;hZ5#qRMm2tO?4@ zNedboOI{Js5r524O8n~@X5Xp0z?c;7WO7*S<~b8p(NIxShK9XA%V5e}Rl7A&Mk21+wOg*dlM3)j-6*dkJ4-LTHm7odWpDXO9ii-UQh}4HTkrVVS8J~_FSJW z<5r#v?FvF!dMBvXPp*z1L65OfzwCQ_^U0RpV%%8d=)lkOc~vY!-BcXx?3QbJ+ga0n zG_{t~^_QueSQ%K*?EM^q!Xv$%^&Rar{k-Cx<0;MoG4Yxb+A7MbKNI~cGjt$tl2RQw zz7ch0r^>Tu#2*Aa{12QuOEnFwk)0etorsy(cH$Mcm(R0R;d&p=t(iMfO{ldLBnm%c z>fD!<_fGwiM;pV^U$VUGvJtZ^9o?{$(@7j6Ue*efP01 zr{}JOi=gJ`qS_Iua@*@*N1D#9UFyGh z-Z`WG&$RQ$@&6=jGfM+0Q0 z4q`dKaO*?pqtrI6=?n^C?RTd-hG}bfo;wIL8v<3Gs(h=rT{tN&dh7F8YfBol>q?e& zEBmsY;&v?xyZDKEdi^9E-L?J0Hr0OqD^w)bekkNzOUv}>tjvhli4`Zb9lYVn`L=)3 z)kkq^I0${9r8jD!ZMwDB$B-IX&YY7}N=dfg!sh*<#s>ScTy&l)#y`YfT7v&=CS52 zB0>D>{SPEc^%IIIw)i-<(US-ocC5sP^H@_MVcyV{iJ?LEz%Xv}TJ>p%DCbWlBOdC!c?ZmLrg4$M{t~j?dXoyf0 zlfCRj<34^%8TzS*>{yXYLFDDxC6-`>xlv$PA$oAFZLqDIe@u@xRS2w?PLDK&3pV;m z$4X3Tv+16I1(k<2lJ>?=04$Qla+~p=Z6|aR=Sv!_yF#seX$D8{g8h};F`C-V7rAVA z3Qeq&s>_X8YCU+WT$11O7R##)T--xK%2wkq_d#nz#;gb~kRCK17Y*CC_Q!N^sX|~K zbD9oZu+XKpwGSU=U(QC2LWhe_jj6-pV?&XGfiDrAL>fzvjx&9C}E{yZYh-M=sy-8FR8* znjsuC;4U~+B+~z~@}1iS&ikI|$0Zi)JDpEW^4SM(kDn;Iv4E_&z_&0Z*u%r7A_E$$ z#{tsOgXqAveEl&UaPA0L@B2mvCRpgwrIV!a>cx5Sjf3NR_GVsV(rI14Ty<6z^B?PGQA06Rr2Sa4`NozUCBmZ^Pm9H;e1e^o#1(!?>^t$b2VJ;GewkqtdJ~SiiB%T)XBO;F zBR3U&);;d=elp#-viIJy#p@{poae(T?!OVbsb9@rC(`=Kyq>K0@?FJ~a{K#QTb@3+ zKz66$OB`xYR(ZN>Q=yTDB(TKkgy_JwMg1`y(00I;)u!pd1q*$!J$b(0p_Q#y3@@!M z`qEIj$iHF54$;~jnU$qiCTng8P85M{IhJs?Gj@6TXJf2_$MX$o6nB>Gy;0Jq`O6Dj z5-IPFJnEro3c^h{k2|%2P=39`N&d#cPq<4>! factomHeight { - log.Errorf("Saved height (%v) > Factom height (%v)", - syncHeight, factomHeight) + // Load and sync all existing and whitelisted chains. + var err error + syncHeight, err = loadChains() + if err != nil { + log.Error(err) return } + if flag.StartScanHeight > -1 { // If -startscanheight was set... if flag.StartScanHeight > int32(factomHeight) { log.Errorf("-startscanheight %v > Factom height (%v)", flag.StartScanHeight, factomHeight) return } - // Warn if we are skipping blocks. - if flag.StartScanHeight > int32(syncHeight)+1 { - log.Warnf("-startscanheight %v skips over %v blocks from the last saved last saved block height which will very likely result in a corrupted database.", + if !flag.IgnoreNewChains() && + flag.StartScanHeight > int32(syncHeight)+1 { + log.Warnf("-startscanheight %v skips over %v blocks from the last saved last saved block height which will result in missing any new FAT Chains created in those blocks.", flag.StartScanHeight, flag.StartScanHeight-int32(syncHeight)-1) } // We start syncing at syncHeight+1, so subtract one. This // overflows for 0 but it's OK as long as we don't rely on the // value until the first scan loop. - setSyncHeight(uint32(flag.StartScanHeight - 1)) + syncHeight = uint32(flag.StartScanHeight - 1) + } else if flag.IgnoreNewChains() { + // We can assume that all chains are synced to their + // chainheads, so we can start at the current height if we are + // ignoring new chains. + syncHeight = factomHeight } else if syncHeight == 0 { // else if the syncHeight has not been set... - const mainnetStart = 163180 - const testnetStart = 60783 - // This is a hacky, unreliable way to determine what network we - // are on. This needs to be replaced with using the actually - // Network ID. - if factomHeight > mainnetStart { - setSyncHeight(mainnetStart) // Set for mainnet - } else if factomHeight > testnetStart { + switch flag.NetworkID { + case factom.Mainnet(): + const mainnetStart = 163180 + syncHeight = mainnetStart // Set for mainnet + case factom.Testnet(): + const testnetStart = 60783 setSyncHeight(testnetStart) // Set for testnet - } else { - var zero uint32 // Avoid constant overflow compile error. - setSyncHeight(zero - 1) // Start scan at 0. + default: + var zero uint32 // Avoid constant overflow compile error. + syncHeight = zero - 1 // Start scan at 0. } } + // Guard against syncing against a network with an earlier blockheight. + if syncHeight > factomHeight { + log.Errorf("Saved height (%v) > Factom height (%v)", + syncHeight, factomHeight) + return + } + + _done := make(chan struct{}) + go engine(stop, _done) + return _done +} + +func engine(stop <-chan struct{}, done chan struct{}) { + // Ensure done is only closed exactly once. + var once sync.Once + exit := func() { once.Do(func() { close(done) }) } + defer exit() - wg := &sync.WaitGroup{} eblocks := make(chan factom.EBlock) // Ensure all workers exit and state is closed when we exit. defer close(eblocks) - defer state.Close() + defer Chains.Close() // Launch workers + var dblock factom.DBlock + wg := &sync.WaitGroup{} const numWorkers = 8 - for i := 0; i < numWorkers; i++ { - go func() { - for eb := range eblocks { // Read until close(eblocks) - if err := state.Process(eb); err != nil { - log.Errorf("ChainID(%v): %v", eb.ChainID, err) - exit() // Tell engine() to exit. - } - wg.Done() + launchWorkers(numWorkers, func() { + for eb := range eblocks { // Read until close(eblocks) + if err := Process(dblock.KeyMR, eb); err != nil { + log.Errorf("ChainID(%v): %v", eb.ChainID, err) + exit() // Tell engine() to exit. } - }() - } + wg.Done() + } + }) - log.Infof("Syncing from block %v to %v...", syncHeight+1, factomHeight) + if !flag.IgnoreNewChains() { + log.Infof("Searching for FAT chains from block %v to %v...", + syncHeight+1, factomHeight) + } var synced bool var retries int64 scanTicker := time.NewTicker(scanInterval) @@ -150,14 +164,13 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Process all new DBlocks sequentially... for h := syncHeight + 1; h <= factomHeight; h++ { // Get DBlock. - var dblock factom.DBlock dblock.Header.Height = h if err := dblock.Get(c); err != nil { log.Errorf("%#v.Get(c): %v", dblock, err) return } - // Queue all EBlocks for processing and wait. + // Queue all EBlocks for processing. wg.Add(len(dblock.EBlocks)) for _, eb := range dblock.EBlocks { eblocks <- eb @@ -168,14 +181,14 @@ func engine(stop <-chan struct{}, done chan struct{}) { select { case <-done: // We cannot consider this DBlock completed. + // Sync height will not be updated for all chains. return default: } // DBlock completed. setSyncHeight(h) - if err := state.SaveHeight(h); err != nil { - log.Errorf("state.SaveHeight(%v): %v", h, err) + if err := Chains.setSync(h, dblock.KeyMR); err != nil { return } @@ -244,3 +257,9 @@ func updateFactomHeight() error { factomHeight = heights.Entry return nil } + +func launchWorkers(num int, job func()) { + for i := 0; i < num; i++ { + go job() + } +} diff --git a/engine/process.go b/engine/process.go new file mode 100644 index 0000000..8474223 --- /dev/null +++ b/engine/process.go @@ -0,0 +1,101 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package engine + +import ( + "fmt" + + jrpc "github.com/AdamSLevy/jsonrpc2/v11" + + "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/flag" +) + +func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { + // Skip ignored chains or EBlocks for heights earlier than this chain's + // head height. + chain := Chains.Get(eb.ChainID) + if chain.IsIgnored() { + return nil + } + if chain.IsUnknown() { + if flag.IgnoreNewChains() { + Chains.ignore(eb.ChainID) + return nil + } + // Load this Entry Block. + if err := eb.Get(c); err != nil { + return fmt.Errorf("%#v.Get(c): %v", eb, err) + } + if !eb.IsFirst() { + Chains.ignore(eb.ChainID) + return nil + } + // Load first entry of new chain. + first := eb.Entries[0] + if err := first.Get(c); err != nil { + return fmt.Errorf("%#v.Get(c): %v", first, err) + } + + // Ignore chains with NameIDs that don't match the fat pattern. + nameIDs := first.ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + Chains.ignore(eb.ChainID) + return nil + } + + var identity factom.Identity + _, identity.ChainID = fat.TokenIssuer(nameIDs) + if err := identity.Get(c); err != nil { + // A jrpc.Error indicates that the identity chain + // doesn't yet exist, which we tolerate. + if _, ok := err.(jrpc.Error); !ok { + return err + } + } + + if err := eb.GetEntries(c); err != nil { + return fmt.Errorf("%#v.GetEntries(c): %v", eb, err) + } + + var err error + chain.Chain, err = db.OpenNew(dbKeyMR, eb, flag.NetworkID, + identity) + if err != nil { + return fmt.Errorf("db.OpenNew(): %v") + } + if chain.Issuance.IsPopulated() { + chain.ChainStatus = ChainStatusIssued + } else { + chain.ChainStatus = ChainStatusTracked + } + Chains.set(chain.ID, chain) + return nil + } + if eb.Height <= chain.Head.Height { + return nil + } + return chain.Apply(c, dbKeyMR, eb) +} diff --git a/factom/dblock_eblock_entry_test.go b/factom/dblock_eblock_entry_test.go index df648e3..2e526d7 100644 --- a/factom/dblock_eblock_entry_test.go +++ b/factom/dblock_eblock_entry_test.go @@ -142,11 +142,11 @@ func TestDataStructures(t *testing.T) { // A bad URL will cause an error. c.FactomdServer = "example.com" - _, err := eb.GetAllPrev(c) + _, err := eb.GetPrevAll(c) assert.Error(err) c.FactomdServer = courtesyNode - ebs, err := eb.GetAllPrev(c) + ebs, err := eb.GetPrevAll(c) var first EBlock if assert.NoError(err) { assert.Len(ebs, 5) @@ -169,7 +169,7 @@ func TestDataStructures(t *testing.T) { require.False(eb2.IsPopulated()) assert.EqualError(eb2.GetFirst(c), `jsonrpc2.Error{Code:-32009, Message:"Missing Chain Head"}`) - ebs, err = eb2.GetAllPrev(c) + ebs, err = eb2.GetPrevAll(c) assert.EqualError(err, `jsonrpc2.Error{Code:-32009, Message:"Missing Chain Head"}`) assert.Nil(ebs) diff --git a/factom/eblock.go b/factom/eblock.go index 2ca3a0a..42e1740 100644 --- a/factom/eblock.go +++ b/factom/eblock.go @@ -139,6 +139,20 @@ func (eb *EBlock) GetChainHead(c *Client) error { return nil } +// GetEntries calls eb.Get and then calls Get on each Entry in eb.Entries. +func (eb *EBlock) GetEntries(c *Client) error { + if err := eb.Get(c); err != nil { + return err + } + for i := range eb.Entries { + e := &eb.Entries[i] + if err := e.Get(c); err != nil { + return err + } + } + return nil +} + // IsFirst returns true if this is the first EBlock in its chain, indicated by // the PrevKeyMR being all zeroes. IsFirst returns false if eb is not populated // or if the PrevKeyMR is not all zeroes. @@ -156,15 +170,15 @@ func (eb EBlock) Prev() EBlock { return EBlock{ChainID: eb.ChainID, KeyMR: eb.PrevKeyMR} } -// GetAllPrev returns a slice of all preceding EBlocks in eb's chain, in order -// from eb to the first EBlock in the chain. So the 0th element of the returned -// slice is always equal to eb. If eb is the first entry block in its chain, -// then it is the only element in the slice. +// GetPrevAll returns a slice of all preceding EBlocks, in order from eb to the +// first EBlock in the chain. So the 0th element of the returned slice is +// always equal to eb. If eb is the first EBlock in the chain, then it is the +// only element in the slice. Like Get, if eb does not have a KeyMR, the chain +// head KeyMR is queried first. // // If you are only interested in obtaining the first entry block in eb's chain, -// and not all of the intermediary ones, then use GetFirst to reduce network -// calls and memory usage. -func (eb EBlock) GetAllPrev(c *Client) ([]EBlock, error) { +// and not all of the intermediary ones, then use GetFirst. +func (eb EBlock) GetPrevAll(c *Client) ([]EBlock, error) { ebs := []EBlock{} for ; !eb.IsFirst(); eb = eb.Prev() { if err := eb.Get(c); err != nil { @@ -175,10 +189,38 @@ func (eb EBlock) GetAllPrev(c *Client) ([]EBlock, error) { return ebs, nil } +// GetPrevUpTo returns a slice of all preceding EBlocks, in order from eb up +// to, but not including, keyMR. So the 0th element of the returned slice is +// always equal to eb. If *eb.KeyMR == keyMR then nil, nil is returned. If +// *eb.PrevKeyMR == keyMR, then it is the only element in the slice. If the +// beginning of the chain is reached without finding keyMR, an error is +// returned with the fully populated []EBlock. Like Get, if eb does not have a +// KeyMR, the chain head KeyMR is queried first. +func (eb EBlock) GetPrevUpTo(c *Client, keyMR Bytes32) ([]EBlock, error) { + ebs := []EBlock{} + if err := eb.Get(c); err != nil { + return nil, err + } + if *eb.KeyMR == keyMR { + return nil, nil + } + for ; *eb.PrevKeyMR != keyMR && !eb.PrevKeyMR.IsZero(); eb = eb.Prev() { + if err := eb.Get(c); err != nil { + return nil, err + } + ebs = append(ebs, eb) + } + if *ebs[len(ebs)-1].PrevKeyMR != keyMR { + return ebs, fmt.Errorf("EBlock{%v} not found prior to EBlock{%v}", + keyMR, eb.KeyMR) + } + return ebs, nil +} + // GetFirst finds the first Entry Block in eb's chain, and populates eb as // such. // -// GetFirst differs from GetAllPrev in that it does not allocate any additional +// GetFirst differs from GetPrevAll in that it does not allocate any additional // EBlocks. GetFirst avoids allocating any new EBlocks by reusing eb to // traverse up to the first entry block. func (eb *EBlock) GetFirst(c *Client) error { @@ -434,3 +476,12 @@ func (eb *EBlock) ComputeKeyMR() (Bytes32, error) { copy(data[i:], bodyMR[:]) return sha256.Sum256(data), nil } + +func (eb *EBlock) SetTimestamp(ts time.Time) { + prevTs := eb.Timestamp + for i := range eb.Entries { + e := &eb.Entries[i] + e.Timestamp = ts.Add(e.Timestamp.Sub(prevTs)) + } + eb.Timestamp = ts +} diff --git a/factom/networkid.go b/factom/networkid.go index 66acfe8..23924e2 100644 --- a/factom/networkid.go +++ b/factom/networkid.go @@ -1,5 +1,10 @@ package factom +import ( + "fmt" + "strings" +) + var ( mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} testnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA3} @@ -20,6 +25,28 @@ func (n NetworkID) String() string { return "custom: 0x" + Bytes(n[:]).String() } } +func (n *NetworkID) Set(netIDStr string) error { + switch strings.ToLower(netIDStr) { + case "main", "mainnet": + *n = Mainnet() + case "test", "testnet": + *n = Testnet() + default: + if netIDStr[:2] == "0x" { + // omit leading 0x + netIDStr = netIDStr[2:] + } + var b Bytes + if err := b.Set(netIDStr); err != nil { + return err + } + if len(b) != len(n[:]) { + return fmt.Errorf("invalid length") + } + copy(n[:], b) + } + return nil +} func (n NetworkID) IsMainnet() bool { return n == mainnetID diff --git a/flag/flag.go b/flag/flag.go index 94a460f..8e5c7fd 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -65,6 +65,13 @@ var ( "ecadr": "ECADR", "esadr": "ESADR", + + "networkid": "NETWORK_ID", + + "whitelist": "WHITELIST", + "blacklist": "BLACKLIST", + "ignorenewchains": "IGNORE_NEW_CHAINS", + "skipdbvalidation": "SKIP_DB_VALIDATION", } defaults = map[string]interface{}{ "startscanheight": uint64(0), @@ -91,6 +98,13 @@ var ( "ecadr": "", "esadr": "", + + "networkid": factom.Mainnet(), + + "whitelist": (Bytes32List)(nil), + "blacklist": (Bytes32List)(nil), + "ignorenewchains": false, + "skipdbvalidation": false, } descriptions = map[string]string{ "startscanheight": "Block height to start scanning for deposits on startup", @@ -107,6 +121,7 @@ var ( "factomdpassword": "Password for API connections to factomd", //"factomdcert": "The TLS certificate that will be provided by the factomd API server", //"factomdtls": "Set to true to use TLS when accessing the factomd API", + "networkid": `Accepts "main", "test", or four bytes in hex`, "w": "IPAddr:port# of factom-walletd API to use to access wallet", "wallettimeout": "Timeout for factom-walletd API requests, 0 means never timeout", @@ -117,6 +132,11 @@ var ( "ecadr": "Entry Credit Public Address to use to pay for Factom entries", "esadr": "Entry Credit Secret Address to use to pay for Factom entries", + + "whitelist": "Track only these chains, adding the database if needed", + "blacklist": "Do not track or sync these chains, overrides -whitelist", + "ignorenewchains": "Do not track new chains, sync existing chain databases", + "skipdbvalidation": "Skip the full validation check of all chain databases", } flags = complete.Flags{ "-startscanheight": complete.PredictAnything, @@ -146,6 +166,14 @@ var ( "-uninstallcompletion": complete.PredictNothing, "-ecadr": predictAddress(false, 1, "-ecadr", ""), + + "-whitelist": complete.PredictAnything, + "-blacklist": complete.PredictAnything, + "-ignorenewchains": complete.PredictNothing, + + "-networkid": complete.PredictSet("main", "test"), + + "-skipdbvalidation": complete.PredictNothing, } startScanHeight uint64 // We parse the flag as unsigned. @@ -161,10 +189,15 @@ var ( APIAddress string FactomClient = factom.NewClient() + NetworkID factom.NetworkID flagset map[string]bool log *logrus.Entry Completion *complete.Complete + + Whitelist, Blacklist Bytes32List + ignoreNewChains bool + SkipDBValidation bool ) func init() { @@ -183,6 +216,7 @@ func init() { flagVar(&FactomClient.Factomd.Timeout, "factomdtimeout") flagVar(&FactomClient.Factomd.User, "factomduser") flagVar(&FactomClient.Factomd.Password, "factomdpassword") + flagVar(&NetworkID, "networkid") //flagVar(&FactomClient.Factomd.TLSCertFile, "factomdcert") //flagVar(&FactomClient.Factomd.TLSEnable, "factomdtls") @@ -193,6 +227,11 @@ func init() { //flagVar(&FactomClient.Walletd.TLSCertFile, "walletcert") //flagVar(&FactomClient.Walletd.TLSEnable, "wallettls") + flagVar(&Whitelist, "whitelist") + flagVar(&Blacklist, "blacklist") + flagVar(&ignoreNewChains, "ignorenewchains") + flagVar(&SkipDBValidation, "skipdbvalidation") + // Add flags for self installing the CLI completion tool Completion = complete.New(os.Args[0], complete.Command{Flags: flags}) Completion.CLI.InstallName = "installcompletion" @@ -366,3 +405,11 @@ func setupLogger() { } log = _log.WithField("pkg", "flag") } + +func HasWhitelist() bool { + return len(Whitelist) > 0 +} + +func IgnoreNewChains() bool { + return ignoreNewChains || HasWhitelist() +} diff --git a/flag/addresslist.go b/flag/list.go similarity index 70% rename from flag/addresslist.go rename to flag/list.go index c561c89..f4ebac7 100644 --- a/flag/addresslist.go +++ b/flag/list.go @@ -25,10 +25,10 @@ package flag import ( "strings" - . "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/factom" ) -type FAAddressList []FAAddress +type FAAddressList []factom.FAAddress func (adrs FAAddressList) String() string { if len(adrs) == 0 { @@ -41,7 +41,7 @@ func (adrs FAAddressList) String() string { return s[:len(s)-1] } -// Set appends a comma seperated list of FAAddresses. +// Set appends a comma seperated list of factom.FAAddresses. func (adrs *FAAddressList) Set(s string) error { adrStrs := strings.Split(s, ",") newAdrs := make(FAAddressList, len(adrStrs)) @@ -53,3 +53,29 @@ func (adrs *FAAddressList) Set(s string) error { *adrs = append(*adrs, newAdrs...) return nil } + +type Bytes32List []factom.Bytes32 + +func (b32s Bytes32List) String() string { + if len(b32s) == 0 { + return "" + } + var s string + for _, b32 := range b32s { + s += b32.String() + "," + } + return s[:len(s)-1] +} + +// Set appends a comma seperated list of factom.FAAddresses. +func (b32s *Bytes32List) Set(s string) error { + b32Strs := strings.Split(s, ",") + newB32s := make(Bytes32List, len(b32Strs)) + for i, b32Str := range b32Strs { + if err := newB32s[i].Set(b32Str); err != nil { + return err + } + } + *b32s = append(*b32s, newB32s...) + return nil +} diff --git a/flag/predict.go b/flag/predict.go index 9945525..dd72de1 100644 --- a/flag/predict.go +++ b/flag/predict.go @@ -28,7 +28,7 @@ import ( "strings" "time" - . "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/posener/complete" ) @@ -87,16 +87,16 @@ func predictAddress(fa bool, num int, flagName, suffix string) complete.PredictF func listAddresses(fa bool) []string { parseWalletFlags() - var adrs []Address + var adrs []factom.Address if fa { as, _ := FactomClient.GetFAAddresses() - adrs = make([]Address, len(as)) + adrs = make([]factom.Address, len(as)) for i, adr := range as { adrs[i] = adr } } else { as, _ := FactomClient.GetECAddresses() - adrs = make([]Address, len(as)) + adrs = make([]factom.Address, len(as)) for i, adr := range as { adrs[i] = adr } @@ -107,7 +107,7 @@ func listAddresses(fa bool) []string { } return adrStrs } -func String(adr Address) string { +func String(adr factom.Address) string { return adr.String() } diff --git a/state/db.go b/state/db.go index 58dc6e1..836208c 100644 --- a/state/db.go +++ b/state/db.go @@ -33,7 +33,7 @@ import ( "github.com/gocraft/dbr" "github.com/gocraft/dbr/dialect" "github.com/jinzhu/gorm" - _ "github.com/jinzhu/gorm/dialects/sqlite" + //_ "github.com/jinzhu/gorm/dialects/sqlite" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat" diff --git a/state/process.go b/state/process.go index bf7de5c..7021c39 100644 --- a/state/process.go +++ b/state/process.go @@ -62,7 +62,6 @@ func (chain *Chain) Process(eb factom.EBlock) error { } // Ignore chains with NameIDs that don't match the fat pattern. - // FAT1 will also match here. if !fat.ValidTokenNameIDs(first.ExtIDs) { chain.ignore() return nil From 0ea09f599b4a73050f957ca7060aa7f18705c47a Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 7 Aug 2019 16:09:08 -0800 Subject: [PATCH 012/124] refactor(engine,srv): Fully integrate package db Integration of the new package db is complete. --- db/address.go | 58 +- db/apply.go | 34 +- db/chain.go | 16 +- db/eblock.go | 47 +- db/entry.go | 153 ++++- db/metadata.go | 28 +- db/nftoken.go | 122 +++- db/schema.go | 162 +++--- db/sql.go | 63 ++ ...bad3f56169f94966341018b1950542f3dd.sqlite3 | Bin 266240 -> 274432 bytes ...b10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 | Bin 106496 -> 114688 bytes db/validate.go | 14 +- engine/chain.go | 89 ++- engine/chainmap.go | 77 ++- engine/engine.go | 31 +- engine/process.go | 63 +- factom/eblock.go | 34 +- factom/entry.go | 9 +- flag/flag.go | 3 + srv/methods.go | 300 ++++------ state/chain.go | 77 --- state/chainmap.go | 70 --- state/chainstatus.go | 58 -- state/chainstatus_test.go | 72 --- state/db.go | 546 ------------------ state/doc.go | 23 - state/process.go | 367 ------------ state/schema.go | 97 ---- 28 files changed, 784 insertions(+), 1829 deletions(-) create mode 100644 db/sql.go delete mode 100644 state/chain.go delete mode 100644 state/chainmap.go delete mode 100644 state/chainstatus.go delete mode 100644 state/chainstatus_test.go delete mode 100644 state/db.go delete mode 100644 state/doc.go delete mode 100644 state/process.go delete mode 100644 state/schema.go diff --git a/db/address.go b/db/address.go index d4c0b6b..fee051e 100644 --- a/db/address.go +++ b/db/address.go @@ -4,14 +4,15 @@ import ( "fmt" "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/fatd/factom" ) func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) { - stmt := chain.Conn.Prep(`INSERT INTO addresses - (address, balance) VALUES (?, ?) - ON CONFLICT(address) DO - UPDATE SET balance = balance + excluded.balance;`) + stmt := chain.Conn.Prep(`INSERT INTO "addresses" + ("address", "balance") VALUES (?, ?) + ON CONFLICT("address") DO + UPDATE SET "balance" = "balance" + "excluded"."balance";`) stmt.BindBytes(1, adr[:]) stmt.BindInt64(2, int64(add)) _, err := stmt.Step() @@ -22,6 +23,10 @@ func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) } func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error) { + if sub == 0 { + // Allow tx's with zeros to result in an INSERT. + return chain.addressAdd(adr, 0) + } id, err := SelectAddressID(chain.Conn, adr) if err != nil { return id, err @@ -29,9 +34,8 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error) if id < 0 { return id, fmt.Errorf("insufficient balance: %v", adr) } - stmt := chain.Conn.Prep(`UPDATE addresses - SET balance = balance - ? - WHERE rowid = ?;`) + stmt := chain.Conn.Prep( + `UPDATE addresses SET balance = balance - ? WHERE rowid = ?;`) stmt.BindInt64(1, int64(sub)) stmt.BindInt64(2, id) if _, err := stmt.Step(); err != nil { @@ -47,37 +51,39 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error) } func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, error) { - stmt := conn.Prep(`SELECT balance FROM addresses WHERE address = ?;`) + stmt := conn.Prep(`SELECT "balance" FROM "addresses" WHERE "address" = ?;`) stmt.BindBytes(1, adr[:]) - hasRow, err := stmt.Step() + bal, err := sqlitex.ResultInt64(stmt) if err != nil { return 0, err } - if !hasRow { - return 0, nil - } - return uint64(stmt.ColumnInt64(0)), nil + return uint64(bal), nil } + func SelectAddressID(conn *sqlite.Conn, adr *factom.FAAddress) (int64, error) { - stmt := conn.Prep(`SELECT rowid FROM addresses WHERE address = ?;`) + stmt := conn.Prep(`SELECT "id" FROM "addresses" WHERE "address" = ?;`) stmt.BindBytes(1, adr[:]) - hasRow, err := stmt.Step() - if err != nil { - return -1, err - } - if !hasRow { - return -1, nil - } - return stmt.ColumnInt64(0), nil + return sqlitex.ResultInt64(stmt) +} + +func SelectAddressCount(conn *sqlite.Conn, nonZeroOnly bool) (int64, error) { + stmt := conn.Prep(`SELECT count(*) FROM "addresses" WHERE "id" != 1 + AND (? OR "balance" > 0);`) + stmt.BindBool(1, !nonZeroOnly) + return sqlitex.ResultInt64(stmt) } -func (chain *Chain) insertAddressTransaction(adrID int64, entryID int64, to bool) error { - stmt := chain.Conn.Prep(`INSERT INTO address_transactions - (address_id, entry_id, sent_to) VALUES +func (chain *Chain) insertAddressTransaction( + adrID int64, entryID int64, to bool) (int64, error) { + stmt := chain.Conn.Prep(`INSERT INTO "address_transactions" + ("address_id", "entry_id", "to") VALUES (?, ?, ?)`) stmt.BindInt64(1, adrID) stmt.BindInt64(2, entryID) stmt.BindBool(3, to) _, err := stmt.Step() - return err + if err != nil { + return -1, err + } + return chain.Conn.LastInsertRowID(), nil } diff --git a/db/apply.go b/db/apply.go index a48b325..a592933 100644 --- a/db/apply.go +++ b/db/apply.go @@ -99,7 +99,7 @@ func (chain *Chain) applyFAT0Tx( if err != nil { return } - if err = chain.insertAddressTransaction(ai, ei, true); err != nil { + if _, err = chain.insertAddressTransaction(ai, ei, true); err != nil { return } } @@ -111,7 +111,7 @@ func (chain *Chain) applyFAT0Tx( err = fmt.Errorf("coinbase exceeds max supply") return } - if err = chain.insertAddressTransaction(1, ei, false); err != nil { + if _, err = chain.insertAddressTransaction(1, ei, false); err != nil { return } err = chain.numIssuedAdd(addIssued) @@ -124,7 +124,7 @@ func (chain *Chain) applyFAT0Tx( if err != nil { return } - if err = chain.insertAddressTransaction(ai, ei, false); err != nil { + if _, err = chain.insertAddressTransaction(ai, ei, false); err != nil { return } } @@ -149,6 +149,10 @@ func (chain *Chain) applyTx(ei int64, e factom.Entry, tx fat.Validator) (bool, e } func (chain *Chain) setApplyFunc() { + if !chain.Issuance.IsPopulated() { + chain.apply = chain.applyIssuance + return + } // Adapt to match ApplyFunc. switch chain.Type { case fat0.Type: @@ -206,7 +210,9 @@ func (chain *Chain) applyFAT1Tx( if err != nil { return } - if err = chain.insertAddressTransaction(ai, ei, true); err != nil { + var adrTxID int64 + adrTxID, err = chain.insertAddressTransaction(ai, ei, true) + if err != nil { return } for nfID := range nfTkns { @@ -214,7 +220,7 @@ func (chain *Chain) applyFAT1Tx( return } if err = chain.insertNFTokenTransaction( - nfID, ei, ai); err != nil { + nfID, adrTxID); err != nil { return } } @@ -228,7 +234,9 @@ func (chain *Chain) applyFAT1Tx( err = fmt.Errorf("coinbase exceeds max supply") return } - if err = chain.insertAddressTransaction(1, ei, false); err != nil { + var adrTxID int64 + adrTxID, err = chain.insertAddressTransaction(1, ei, false) + if err != nil { return } for nfID := range nfTkns { @@ -239,6 +247,10 @@ func (chain *Chain) applyFAT1Tx( if err = chain.setNFTokenMetadata(nfID, metadata); err != nil { return } + if err = chain.insertNFTokenTransaction( + nfID, adrTxID); err != nil { + return + } } err = chain.numIssuedAdd(addIssued) return @@ -250,9 +262,17 @@ func (chain *Chain) applyFAT1Tx( if err != nil { return } - if err = chain.insertAddressTransaction(ai, ei, false); err != nil { + var adrTxID int64 + adrTxID, err = chain.insertAddressTransaction(ai, ei, false) + if err != nil { return } + for nfID := range nfTkns { + if err = chain.insertNFTokenTransaction( + nfID, adrTxID); err != nil { + return + } + } } return } diff --git a/db/chain.go b/db/chain.go index 4766508..0d5a32a 100644 --- a/db/chain.go +++ b/db/chain.go @@ -50,7 +50,7 @@ type Chain struct { } func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, - identity factom.Identity) (*Chain, error) { + identity factom.Identity) (chain *Chain, err error) { fname := eb.ChainID.String() + dbFileExtension path := flag.DBPath + "/" + fname @@ -60,7 +60,7 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network } // Ensure that the database file doesn't already exist. - _, err := os.Stat(path) + _, err = os.Stat(path) if err == nil { return nil, fmt.Errorf("already exists: %v", path) } @@ -68,10 +68,18 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network return nil, err } - chain, err := open(fname) + chain, err = open(fname) if err != nil { return nil, err } + defer func() { + if err != nil { + chain.Close() + if err := os.Remove(path); err != nil { + chain.Log.Errorf("os.Remove(): %v", err) + } + } + }() chain.ID = eb.ChainID chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) chain.DBKeyMR = dbKeyMR @@ -90,7 +98,7 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network return nil, err } - chain.apply = chain.applyIssuance + chain.setApplyFunc() if err := chain.Apply(dbKeyMR, eb); err != nil { return nil, err } diff --git a/db/eblock.go b/db/eblock.go index b155543..ec692ba 100644 --- a/db/eblock.go +++ b/db/eblock.go @@ -18,10 +18,10 @@ func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) erro var data []byte data, err = eb.MarshalBinary() if err != nil { - return fmt.Errorf("factom.EBlock{}.MarshalBinary(): %v", err) + panic(fmt.Errorf("factom.EBlock{}.MarshalBinary(): %v", err)) } - stmt := chain.Conn.Prep(`INSERT INTO eblocks - (seq, key_mr, db_height, db_key_mr, timestamp, data) + stmt := chain.Conn.Prep(`INSERT INTO "eblocks" + ("seq", "key_mr", "db_height", "db_key_mr", "timestamp", "data") VALUES (?, ?, ?, ?, ?, ?);`) stmt.BindInt64(1, int64(eb.Sequence)) stmt.BindBytes(2, eb.KeyMR[:]) @@ -36,11 +36,11 @@ func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) erro // SelectEBlockWhere is a SQL fragment that must be appended with the condition // of a WHERE clause and a final semi-colon. -const SelectEBlockWhere = `SELECT key_mr, data, timestamp FROM eblocks WHERE ` +const SelectEBlockWhere = `SELECT "key_mr", "data", "timestamp" FROM "eblocks" WHERE ` // SelectEBlock uses stmt to populate and return a new factom.EBlock. Since // column position is used to address the data, the stmt must start with -// `SELECT key_mr, data, timestamp`. This can be called repeatedly until +// `SELECT "key_mr", "data", "timestamp"`. This can be called repeatedly until // stmt.Step() returns false, in which case the returned factom.EBlock will not // be populated. func SelectEBlock(stmt *sqlite.Stmt) (factom.EBlock, error) { @@ -55,7 +55,7 @@ func SelectEBlock(stmt *sqlite.Stmt) (factom.EBlock, error) { eb.KeyMR = new(factom.Bytes32) if stmt.ColumnBytes(0, eb.KeyMR[:]) != len(eb.KeyMR) { - return eb, fmt.Errorf("invalid key_mr length") + panic("invalid key_mr length") } // Load timestamp so that entries have correct timestamps. @@ -64,39 +64,41 @@ func SelectEBlock(stmt *sqlite.Stmt) (factom.EBlock, error) { data := make([]byte, stmt.ColumnLen(1)) stmt.ColumnBytes(1, data) if err := eb.UnmarshalBinary(data); err != nil { - return eb, fmt.Errorf("factom.EBlock{}.UnmarshalBinary(%x): %v", - data, err) + panic(fmt.Errorf("factom.EBlock{}.UnmarshalBinary(%x): %v", data, err)) } return eb, nil } func SelectEBlockByHeight(conn *sqlite.Conn, height uint32) (factom.EBlock, error) { - stmt := conn.Prep(SelectEBlockWhere + `db_height = ?;`) + stmt := conn.Prep(SelectEBlockWhere + `"db_height" = ?;`) stmt.BindInt64(1, int64(height)) + defer stmt.Reset() return SelectEBlock(stmt) } func SelectEBlockBySequence(conn *sqlite.Conn, seq uint32) (factom.EBlock, error) { - stmt := conn.Prep(SelectEBlockWhere + `seq = ?;`) + stmt := conn.Prep(SelectEBlockWhere + `"seq" = ?;`) stmt.BindInt64(1, int64(seq)) + defer stmt.Reset() return SelectEBlock(stmt) } func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { var keyMR factom.Bytes32 - stmt := conn.Prep(`SELECT key_mr FROM eblocks WHERE seq = ?;`) + stmt := conn.Prep(`SELECT "key_mr" FROM "eblocks" WHERE "seq" = ?;`) stmt.BindInt64(1, int64(int32(seq))) // Preserve uint32(-1) as -1 hasRow, err := stmt.Step() + defer stmt.Reset() if err != nil { return keyMR, err } if !hasRow { - return keyMR, nil + return keyMR, fmt.Errorf("no such EBlock{Sequence: %v}", seq) } if stmt.ColumnBytes(0, keyMR[:]) != len(keyMR) { - return keyMR, fmt.Errorf("invalid key_mr length") + panic("invalid key_mr length") } return keyMR, nil @@ -104,37 +106,40 @@ func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { func SelectDBKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { var dbKeyMR factom.Bytes32 - stmt := conn.Prep(`SELECT db_key_mr FROM eblocks WHERE seq = ?;`) + stmt := conn.Prep(`SELECT "db_key_mr" FROM "eblocks" WHERE "seq" = ?;`) stmt.BindInt64(1, int64(int32(seq))) // Preserve uint32(-1) as -1 hasRow, err := stmt.Step() + defer stmt.Reset() if err != nil { return dbKeyMR, err } if !hasRow { - return dbKeyMR, nil + return dbKeyMR, fmt.Errorf("no such EBlock{Sequence: %v}", seq) } if stmt.ColumnBytes(0, dbKeyMR[:]) != len(dbKeyMR) { - return dbKeyMR, fmt.Errorf("invalid key_mr length") + panic("invalid key_mr length") } return dbKeyMR, nil } -func SelectLatestEBlock(conn *sqlite.Conn) (factom.EBlock, factom.Bytes32, error) { +func SelectEBlockLatest(conn *sqlite.Conn) (factom.EBlock, factom.Bytes32, error) { var dbKeyMR factom.Bytes32 - stmt := conn.Prep(`SELECT key_mr, data, timestamp, db_key_mr FROM eblocks - WHERE seq = (SELECT MAX(seq) FROM eblocks);`) + stmt := conn.Prep( + `SELECT "key_mr", "data", "timestamp", "db_key_mr" FROM "eblocks" + WHERE "seq" = (SELECT max("seq") FROM "eblocks");`) eb, err := SelectEBlock(stmt) + defer stmt.Reset() if err != nil { return eb, dbKeyMR, err } if !eb.IsPopulated() { - return eb, dbKeyMR, nil + panic("no EBlocks") } if stmt.ColumnBytes(3, dbKeyMR[:]) != len(dbKeyMR) { - return eb, dbKeyMR, fmt.Errorf("invalid db_key_mr length") + panic("invalid db_key_mr length") } return eb, dbKeyMR, nil diff --git a/db/entry.go b/db/entry.go index 3640abf..3f5cf42 100644 --- a/db/entry.go +++ b/db/entry.go @@ -2,20 +2,23 @@ package db import ( "fmt" + "strings" "time" "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) func (chain *Chain) insertEntry(e factom.Entry, ebSeq uint32) (int64, error) { data, err := e.MarshalBinary() if err != nil { - return -1, fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err) + panic(fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err)) } - stmt := chain.Conn.Prep(`INSERT INTO entries - (eb_seq, timestamp, hash, data) + stmt := chain.Conn.Prep(`INSERT INTO "entries" + ("eb_seq", "timestamp", "hash", "data") VALUES (?, ?, ?, ?);`) stmt.BindInt64(1, int64(int32(ebSeq))) // Preserve uint32(-1) as -1 stmt.BindInt64(2, int64(e.Timestamp.Unix())) @@ -23,19 +26,19 @@ func (chain *Chain) insertEntry(e factom.Entry, ebSeq uint32) (int64, error) { stmt.BindBytes(4, data) if _, err := stmt.Step(); err != nil { - return 0, err + return -1, err } return chain.Conn.LastInsertRowID(), nil } func (chain *Chain) setEntryValid(id int64) error { - stmt := chain.Conn.Prep(`UPDATE entries SET valid = 1 WHERE id = ?;`) + stmt := chain.Conn.Prep(`UPDATE "entries" SET "valid" = 1 WHERE "id" = ?;`) stmt.BindInt64(1, id) _, err := stmt.Step() return err } -const SelectEntryWhere = `SELECT hash, data, timestamp FROM entries WHERE ` +const SelectEntryWhere = `SELECT "hash", "data", "timestamp" FROM "entries" WHERE ` func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { var e factom.Entry @@ -49,14 +52,14 @@ func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { e.Hash = new(factom.Bytes32) if stmt.ColumnBytes(0, e.Hash[:]) != len(e.Hash) { - return e, fmt.Errorf("invalid hash length") + panic("invalid hash length") } data := make([]byte, stmt.ColumnLen(1)) stmt.ColumnBytes(1, data) if err := e.UnmarshalBinary(data); err != nil { - return e, fmt.Errorf("factom.Entry{}.UnmarshalBinary(%x): %v", - data, err) + panic(fmt.Errorf("factom.Entry{}.UnmarshalBinary(%x): %v", + data, err)) } e.Timestamp = time.Unix(stmt.ColumnInt64(2), 0) @@ -65,34 +68,146 @@ func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { } func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + `id = ?;`) + stmt := conn.Prep(SelectEntryWhere + `"id" = ?;`) stmt.BindInt64(1, id) + defer stmt.Reset() return SelectEntry(stmt) } func SelectEntryByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + `hash = ?;`) + stmt := conn.Prep(SelectEntryWhere + `"hash" = ?;`) + defer stmt.Reset() return SelectEntry(stmt) } -func SelectValidEntryByHash(conn *sqlite.Conn, +func SelectEntryByHashValid(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + `hash = ? AND valid = true;`) + stmt := conn.Prep(SelectEntryWhere + `"hash" = ? AND "valid" = true;`) + defer stmt.Reset() return SelectEntry(stmt) } +func SelectEntryCount(conn *sqlite.Conn, validOnly bool) (int64, error) { + stmt := conn.Prep(`SELECT count(*) FROM "entries" WHERE (? OR "valid" = true);`) + stmt.BindBool(1, !validOnly) + return sqlitex.ResultInt64(stmt) +} + +func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, + adrs []factom.FAAddress, nfTkns fat1.NFTokens, + toFrom, order string, + page, limit int64) ([]factom.Entry, error) { + var sql sql + sql.Append(SelectEntryWhere + `"valid" = true`) + if startHash != nil { + sql.Append(` AND "id" >= (SELECT "id" FROM "entries" WHERE "hash" = ?)`, + func(s *sqlite.Stmt, c int) int { + s.BindBytes(c, startHash[:]) + return 1 + }) + } + var to bool + switch strings.ToLower(toFrom) { + case "to": + to = true + case "from": + case "": + default: + panic(fmt.Errorf("invalid toFrom: %v", toFrom)) + } + if len(nfTkns) > 0 { + sql.Append(` AND "id" IN ( + SELECT "entry_id" FROM "nf_token_address_transactions" + WHERE "nf_token_id" IN (`) // 2 open ( + sql.Bind(len(nfTkns), func(s *sqlite.Stmt, c int) int { + i := 0 + for nfTkn := range nfTkns { + s.BindInt64(c+i, int64(nfTkn)) + i++ + } + return len(nfTkns) + }) + sql.Append(`)`) // 1 open ( + if len(adrs) > 0 { + sql.Append(` AND "owner_id" IN ( + SELECT "id" FROM "addresses" + WHERE "address" IN (`) // 3 open ( + sql.Bind(len(adrs), func(s *sqlite.Stmt, c int) int { + for i, adr := range adrs { + s.BindBytes(c+i, adr[:]) + } + return len(adrs) + }) + sql.Append(`))`) // 1 open ( + } + if len(toFrom) > 0 { + sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, c int) int { + s.BindBool(c, to) + return 1 + }) + } + sql.Append(`)`) // 0 open { + } else if len(adrs) > 0 { + sql.Append(` AND "id" IN ( + SELECT "entry_id" FROM "address_transactions" + WHERE "address" IN (?`) // 2 open ( + + sql.Bind(len(adrs), func(s *sqlite.Stmt, c int) int { + for i, adr := range adrs { + s.BindBytes(c+i, adr[:]) + } + return len(adrs) + }) + sql.Append(`)`) // 1 open ( + if len(toFrom) > 0 { + sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, c int) int { + s.BindBool(c, to) + return 1 + }) + } + sql.Append(`)`) // 0 open ( + } + + sql.OrderPaginate(order, page, limit) + + stmt := sql.Prep(conn) + defer stmt.Reset() + + var entries []factom.Entry + for { + e, err := SelectEntry(stmt) + if err != nil { + return nil, err + } + if !e.IsPopulated() { + break + } + entries = append(entries, e) + } + + return entries, nil +} + func checkEntryUniqueValid(conn *sqlite.Conn, id int64, hash *factom.Bytes32) (bool, error) { - stmt := conn.Prep(`SELECT count(*) FROM entries WHERE - valid = true AND id < ? AND hash = ?;`) + stmt := conn.Prep(`SELECT count(*) FROM "entries" WHERE + "valid" = true AND "id" < ? AND "hash" = ?;`) stmt.BindInt64(1, id) stmt.BindBytes(2, hash[:]) - hasRow, err := stmt.Step() + val, err := sqlitex.ResultInt(stmt) if err != nil { return false, err } - if !hasRow { - panic("should always return one row") + return val == 0, nil +} + +func SelectEntryLatestValid(conn *sqlite.Conn) (factom.Entry, error) { + stmt := conn.Prep(SelectEntryWhere + + `"id" = (SELECT max("id") FROM "entries" WHERE "valid" = true);`) + e, err := SelectEntry(stmt) + defer stmt.Reset() + if err != nil { + return e, err } - return stmt.ColumnInt(0) == 0, nil + return e, nil } diff --git a/db/metadata.go b/db/metadata.go index 49168ac..23dce0a 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -9,8 +9,9 @@ import ( ) func (chain *Chain) insertMetadata() error { - stmt := chain.Conn.Prep(`INSERT INTO metadata - (id, sync_height, sync_db_key_mr, network_id, id_key_entry, id_key_height) + stmt := chain.Conn.Prep(`INSERT INTO "metadata" + ("id", "sync_height", "sync_db_key_mr", + "network_id", "id_key_entry", "id_key_height") VALUES (0, ?, ?, ?, ?, ?);`) stmt.BindInt64(1, int64(chain.SyncHeight)) stmt.BindBytes(2, chain.SyncDBKeyMR[:]) @@ -20,7 +21,6 @@ func (chain *Chain) insertMetadata() error { if err != nil { return err } - fmt.Printf("bind bytes %x\n", data) stmt.BindBytes(4, data) stmt.BindInt64(5, int64(chain.Identity.Height)) } else { @@ -35,8 +35,8 @@ func (chain *Chain) SetSync(height uint32, dbKeyMR *factom.Bytes32) error { if height <= chain.SyncHeight { return nil } - stmt := chain.Conn.Prep(`UPDATE metadata SET - (sync_height, sync_db_key_mr) = (?, ?) WHERE id = 0;`) + stmt := chain.Conn.Prep(`UPDATE "metadata" SET + ("sync_height", "sync_db_key_mr") = (?, ?) WHERE "id" = 0;`) stmt.BindInt64(1, int64(height)) stmt.BindBytes(2, dbKeyMR[:]) _, err := stmt.Step() @@ -49,8 +49,8 @@ func (chain *Chain) SetSync(height uint32, dbKeyMR *factom.Bytes32) error { } func (chain *Chain) setInitEntryID(id int64) error { - stmt := chain.Conn.Prep(`UPDATE metadata SET - (init_entry_id, num_issued) = (?, 0) WHERE id = 0;`) + stmt := chain.Conn.Prep(`UPDATE "metadata" SET + ("init_entry_id", "num_issued") = (?, 0) WHERE "id" = 0;`) stmt.BindInt64(1, id) _, err := stmt.Step() if chain.Conn.Changes() == 0 { @@ -60,8 +60,8 @@ func (chain *Chain) setInitEntryID(id int64) error { } func (chain *Chain) numIssuedAdd(add uint64) error { - stmt := chain.Conn.Prep(`UPDATE metadata SET - num_issued = num_issued + ? WHERE id = 0;`) + stmt := chain.Conn.Prep(`UPDATE "metadata" SET + "num_issued" = "num_issued" + ? WHERE "id" = 0;`) stmt.BindInt64(1, int64(add)) _, err := stmt.Step() if chain.Conn.Changes() == 0 { @@ -72,6 +72,7 @@ func (chain *Chain) numIssuedAdd(add uint64) error { } func (chain *Chain) loadMetadata() error { + defer chain.setApplyFunc() // Load NameIDs first, err := SelectEntryByID(chain.Conn, 1) if err != nil { @@ -88,7 +89,7 @@ func (chain *Chain) loadMetadata() error { chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) // Load Chain Head - eb, dbKeyMR, err := SelectLatestEBlock(chain.Conn) + eb, dbKeyMR, err := SelectEBlockLatest(chain.Conn) if err != nil { return err } @@ -100,9 +101,11 @@ func (chain *Chain) loadMetadata() error { chain.DBKeyMR = &dbKeyMR chain.ID = eb.ChainID - stmt := chain.Conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id, - id_key_entry, id_key_height, init_entry_id, num_issued FROM metadata;`) + stmt := chain.Conn.Prep(`SELECT "sync_height", "sync_db_key_mr", "network_id", + "id_key_entry", "id_key_height", "init_entry_id", "num_issued" + FROM "metadata";`) hasRow, err := stmt.Step() + defer stmt.Reset() if err != nil { return err } @@ -150,7 +153,6 @@ func (chain *Chain) loadMetadata() error { if err := chain.Issuance.Validate(chain.ID1); err != nil { return err } - chain.setApplyFunc() chain.NumIssued = uint64(stmt.ColumnInt64(6)) diff --git a/db/nftoken.go b/db/nftoken.go index b60abf6..ad575e8 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -3,14 +3,16 @@ package db import ( "encoding/json" + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, aID, eID int64) error { - stmt := chain.Conn.Prep(`INSERT INTO nf_tokens - (id, owner_id, creation_entry_id) VALUES (?, ?, ?) - ON CONFLICT(id) DO - UPDATE SET owner_id = excluded.owner_id;`) + stmt := chain.Conn.Prep(`INSERT INTO "nf_tokens" + ("id", "owner_id", "creation_entry_id") VALUES (?, ?, ?) + ON CONFLICT("id") DO + UPDATE SET "owner_id" = "excluded"."owner_id";`) stmt.BindInt64(1, int64(nfID)) stmt.BindInt64(2, aID) stmt.BindInt64(3, eID) @@ -20,8 +22,8 @@ func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, aID, eID int64) error { func (chain *Chain) setNFTokenMetadata( nfID fat1.NFTokenID, metadata json.RawMessage) error { - stmt := chain.Conn.Prep(`UPDATE nf_tokens - SET metadata = ? WHERE id = ?;`) + stmt := chain.Conn.Prep(`UPDATE "nf_tokens" + SET "metadata" = ? WHERE "id" = ?;`) stmt.BindBytes(1, metadata) stmt.BindInt64(2, int64(nfID)) _, err := stmt.Step() @@ -31,12 +33,108 @@ func (chain *Chain) setNFTokenMetadata( return err } -func (chain *Chain) insertNFTokenTransaction(nfID fat1.NFTokenID, eID, aID int64) error { - stmt := chain.Conn.Prep(`INSERT INTO nf_token_transactions - (entry_id, nf_token_id, owner_id) VALUES (?, ?, ?);`) - stmt.BindInt64(1, eID) - stmt.BindInt64(2, int64(nfID)) - stmt.BindInt64(3, aID) +func (chain *Chain) insertNFTokenTransaction(nfID fat1.NFTokenID, adrTxID int64) error { + stmt := chain.Conn.Prep(`INSERT INTO "nf_token_transactions" + ("nf_tkn_id", "adr_tx_id") VALUES (?, ?);`) + stmt.BindInt64(1, int64(nfID)) + stmt.BindInt64(2, adrTxID) _, err := stmt.Step() return err } + +func SelectNFToken(conn *sqlite.Conn, + nfTkn fat1.NFTokenID) (factom.FAAddress, factom.Bytes32, []byte, error) { + var owner factom.FAAddress + var creationHash factom.Bytes32 + stmt := conn.Prep(`SELECT "owner", "metadata", "creation_hash" + FROM "nf_tokens_addresses" WHERE "id" = ?;`) + stmt.BindInt64(1, int64(nfTkn)) + hasRow, err := stmt.Step() + if err != nil { + stmt.Reset() + return owner, creationHash, nil, err + } + if !hasRow { + return owner, creationHash, nil, nil + } + defer stmt.Reset() + if stmt.ColumnBytes(0, owner[:]) != len(owner) { + panic("invalid address length") + } + metadata := make([]byte, stmt.ColumnLen(1)) + stmt.ColumnBytes(1, metadata) + + if stmt.ColumnBytes(2, creationHash[:]) != len(creationHash) { + panic("invalid hash length") + } + return owner, creationHash, metadata, nil +} + +func SelectNFTokens(conn *sqlite.Conn, + order string, page, limit int64) ([]fat1.NFTokenID, + []factom.FAAddress, []factom.Bytes32, [][]byte, error) { + stmt := conn.Prep(`SELECT "id", "owner", "creation_hash", "metadata" + FROM "nf_tokens_addresses";`) + defer stmt.Reset() + + var tkns []fat1.NFTokenID + var owners []factom.FAAddress + var creationHashes []factom.Bytes32 + var metadata [][]byte + for { + hasRow, err := stmt.Step() + if err != nil { + return nil, nil, nil, nil, err + } + if !hasRow { + break + } + tkns = append(tkns, fat1.NFTokenID(stmt.ColumnInt64(0))) + + var owner factom.FAAddress + if stmt.ColumnBytes(1, owner[:]) != len(owner) { + stmt.Reset() + panic("invalid address length") + } + owners = append(owners, owner) + + var creationHash factom.Bytes32 + if stmt.ColumnBytes(2, creationHash[:]) != len(creationHash) { + stmt.Reset() + panic("invalid hash length") + } + creationHashes = append(creationHashes, creationHash) + + data := make([]byte, stmt.ColumnLen(3)) + stmt.ColumnBytes(3, data) + metadata = append(metadata, data) + } + return tkns, owners, creationHashes, metadata, nil +} + +func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, + page, limit int64, order string) ([]fat1.NFTokenID, error) { + var sql sql + sql.Append(`SELECT "id" FROM "nf_tokens" WHERE "owner_id" = ( + SELECT "id" FROM "addresses" WHERE "address" = ?)`, + func(s *sqlite.Stmt, c int) int { + s.BindBytes(c, adr[:]) + return 1 + }) + sql.OrderPaginate(order, page, limit) + + stmt := sql.Prep(conn) + defer stmt.Reset() + var nfTkns []fat1.NFTokenID + for { + hasRow, err := stmt.Step() + if err != nil { + return nil, err + } + if !hasRow { + break + } + nfTkns = append(nfTkns, fat1.NFTokenID(stmt.ColumnInt64(0))) + } + return nfTkns, nil +} diff --git a/db/schema.go b/db/schema.go index 7814271..166dbfc 100644 --- a/db/schema.go +++ b/db/schema.go @@ -8,120 +8,104 @@ import ( ) const ( - createTableEBlocks = `CREATE TABLE eblocks ( - seq INTEGER PRIMARY KEY, - key_mr BLOB NOT NULL UNIQUE, - db_height INTEGER NOT NULL UNIQUE, - db_key_mr BLOB NOT NULL UNIQUE, - timestamp INTEGER NOT NULL, - data BLOB NOT NULL + createTableEBlocks = `CREATE TABLE "eblocks" ( + "seq" INTEGER PRIMARY KEY, + "key_mr" BLOB NOT NULL UNIQUE, + "db_height" INTEGER NOT NULL UNIQUE, + "db_key_mr" BLOB NOT NULL UNIQUE, + "timestamp" INTEGER NOT NULL, + "data" BLOB NOT NULL ); ` - createTableEntries = `CREATE TABLE entries ( - id INTEGER PRIMARY KEY, - eb_seq INTEGER NOT NULL, - timestamp INTEGER NOT NULL, - valid BOOL NOT NULL DEFAULT FALSE, - hash BLOB NOT NULL, - data BLOB NOT NULL, + createTableEntries = `CREATE TABLE "entries" ( + "id" INTEGER PRIMARY KEY, + "eb_seq" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "valid" BOOL NOT NULL DEFAULT FALSE, + "hash" BLOB NOT NULL, + "data" BLOB NOT NULL, - FOREIGN KEY(eb_seq) REFERENCES eblocks + FOREIGN KEY("eb_seq") REFERENCES "eblocks" ); -CREATE INDEX idx_entries_eb_seq ON entries(eb_seq); -CREATE INDEX idx_entries_hash ON entries(hash); +CREATE INDEX "idx_entries_eb_seq" ON "entries"("eb_seq"); +CREATE INDEX "idx_entries_hash" ON "entries"("hash"); ` - createTableTempEntries = `CREATE TABLE temp.entries ( - id INTEGER PRIMARY KEY, - timestamp INTEGER NOT NULL, - valid BOOL NOT NULL DEFAULT FALSE, - hash BLOB NOT NULL, - data BLOB NOT NULL, + createTableAddresses = `CREATE TABLE "addresses" ( + "id" INTEGER PRIMARY KEY, + "address" BLOB NOT NULL UNIQUE, + "balance" INTEGER NOT NULL + CONSTRAINT "insufficient balance" CHECK ("balance" >= 0) ); -CREATE INDEX temp.idx_entries_hash ON entries(hash); ` - createTableAddresses = `CREATE TABLE addresses ( - address BLOB PRIMARY KEY, - balance INTEGER NOT NULL - CONSTRAINT "insufficient balance" CHECK (balance >= 0) -); -` - - createTableTempAddresses = `CREATE TABLE temp.addresses ( - address BLOB NOT NULL UNIQUE, - balance INTEGER NOT NULL - CONSTRAINT "insufficient balance" CHECK (balance >= 0) -); -` - - createTableAddressTransactions = `CREATE TABLE address_transactions ( - entry_id INTEGER NOT NULL, - address_id INTEGER NOT NULL, - sent_to BOOL NOT NULL, - - PRIMARY KEY(entry_id, address_id), - - FOREIGN KEY(entry_id) REFERENCES entries, - FOREIGN KEY(address_id) REFERENCES addresses -); -CREATE INDEX idx_address_transactions_address_id ON address_transactions(address_id); -` - - createTableTempAddressTransactions = `CREATE TABLE temp.address_transactions ( - entry_id INTEGER NOT NULL, - address_id INTEGER NOT NULL, - sent_to BOOL NOT NULL, + createTableAddressTransactions = `CREATE TABLE "address_transactions" ( + "entry_id" INTEGER NOT NULL, + "address_id" INTEGER NOT NULL, + "to" BOOL NOT NULL, - PRIMARY KEY(entry_id, address_id), + PRIMARY KEY("entry_id", "address_id"), - FOREIGN KEY(entry_id) REFERENCES entries, - FOREIGN KEY(address_id) REFERENCES addresses + FOREIGN KEY("entry_id") REFERENCES "entries", + FOREIGN KEY("address_id") REFERENCES "addresses" ); -CREATE INDEX temp.idx_address_transactions_address_id ON address_transactions(address_id); +CREATE INDEX "idx_address_transactions_address_id" ON "address_transactions"("address_id"); ` - createTableNFTokens = `CREATE TABLE nf_tokens ( - id INTEGER PRIMARY KEY, - metadata BLOB, - creation_entry_id INTEGER NOT NULL, - owner_id INTEGER NOT NULL, + createTableNFTokens = `CREATE TABLE "nf_tokens" ( + "id" INTEGER PRIMARY KEY, + "metadata" BLOB, + "creation_entry_id" INTEGER NOT NULL, + "owner_id" INTEGER NOT NULL, - FOREIGN KEY(creation_entry_id) REFERENCES entries, - FOREIGN KEY(owner_id) REFERENCES addresses + FOREIGN KEY("creation_entry_id") REFERENCES "entries", + FOREIGN KEY("owner_id") REFERENCES "addresses" ); -CREATE INDEX idx_nf_tokens_metadata ON nf_tokens(metadata); -CREATE INDEX idx_nf_tokens_owner_id ON nf_tokens(owner_id); +CREATE INDEX "idx_nf_tokens_metadata" ON nf_tokens("metadata"); +CREATE INDEX "idx_nf_tokens_owner_id" ON nf_tokens("owner_id"); +CREATE VIEW "nf_tokens_addresses" AS + SELECT "nf_tokens"."id" AS "id", + "metadata", + "hash" AS "creation_hash", + "address" AS "owner" FROM + "nf_tokens", "addresses", "entries" ON + "owner_id" = "addresses"."id" AND + "creation_entry_id" = "entries"."id"; ` - createTableNFTokensTransactions = `CREATE TABLE nf_token_transactions ( - entry_id INTEGER NOT NULL, - nf_token_id INTEGER NOT NULL, - owner_id INTEGER NOT NULL, + createTableNFTokensTransactions = `CREATE TABLE "nf_token_transactions" ( + "adr_tx_id" INTEGER NOT NULL, + "nf_tkn_id" INTEGER NOT NULL, - PRIMARY KEY(entry_id, nf_token_id), + UNIQUE("adr_tx_id", "nf_tkn_id"), - FOREIGN KEY(entry_id) REFERENCES entries, - FOREIGN KEY(nf_token_id) REFERENCES nf_tokens, - FOREIGN KEY(owner_id) REFERENCES addresses + FOREIGN KEY("nf_tkn_id") REFERENCES "nf_tokens", + FOREIGN KEY("adr_tx_id") REFERENCES "address_transactions" ); -CREATE INDEX idx_nf_token_transactions_owner_id ON nf_token_transactions(owner_id); -CREATE INDEX idx_nf_token_transactions_nf_token_id ON nf_token_transactions(nf_token_id); +CREATE INDEX "idx_nf_token_transactions_adr_tx_id" ON + "nf_token_transactions"("adr_tx_id"); +CREATE INDEX "idx_nf_token_transactions_nf_tkn_id" ON + "nf_token_transactions"("nf_tkn_id"); +CREATE VIEW "nf_token_address_transactions" AS + SELECT "entry_id", "address_id", "nf_tkn_id", "to" FROM + "address_transactions" AS "adr_tx", + "nf_token_transactions" AS "tkn_tx" + ON "adr_tx"."rowid" = "tkn_tx"."adr_tx_id"; ` - createTableMetadata = `CREATE TABLE metadata ( - id INTEGER PRIMARY KEY, - sync_height INTEGER NOT NULL, - sync_db_key_mr BLOB NOT NULL, - network_id BLOB NOT NULL, - id_key_entry BLOB, - id_key_height INTEGER, + createTableMetadata = `CREATE TABLE "metadata" ( + "id" INTEGER PRIMARY KEY, + "sync_height" INTEGER NOT NULL, + "sync_db_key_mr" BLOB NOT NULL, + "network_id" BLOB NOT NULL, + "id_key_entry" BLOB, + "id_key_height" INTEGER, - init_entry_id INTEGER, - num_issued INTEGER, + "init_entry_id" INTEGER, + "num_issued" INTEGER, - FOREIGN KEY(init_entry_id) REFERENCES entries + FOREIGN KEY("init_entry_id") REFERENCES "entries" ); ` @@ -157,7 +141,7 @@ func validateOrApplySchema(conn *sqlite.Conn, schema string) error { return nil } func getFullSchema(conn *sqlite.Conn) (string, error) { - const selectSchema = `SELECT sql FROM sqlite_master;` + const selectSchema = `SELECT "sql" FROM "sqlite_master";` var schema string err := sqlitex.ExecTransient(conn, selectSchema, func(stmt *sqlite.Stmt) error { diff --git a/db/sql.go b/db/sql.go new file mode 100644 index 0000000..175f452 --- /dev/null +++ b/db/sql.go @@ -0,0 +1,63 @@ +package db + +import ( + "fmt" + "strings" + + "crawshaw.io/sqlite" +) + +type bindFunc func(*sqlite.Stmt, int) int + +type sql struct { + sql string + binds []bindFunc +} + +func (sql sql) Prep(conn *sqlite.Conn) *sqlite.Stmt { + sql.sql += `;` + stmt := conn.Prep(sql.sql) + col := 1 + for _, bind := range sql.binds { + col += bind(stmt, col) + } + return stmt +} + +func (sql *sql) Append(str string, binds ...bindFunc) { + sql.sql += str + sql.binds = append(sql.binds, binds...) +} +func (sql *sql) Bind(n int, binds ...bindFunc) { + str := strings.TrimRight(strings.Repeat("?, ", n), ", ") + sql.Append(str, binds...) +} + +const MaxLimit = 600 + +func (sql *sql) Paginate(page, limit int64) { + if limit == 0 || limit > MaxLimit { + limit = MaxLimit + } + sql.Append(` LIMIT ?, ?`, func(s *sqlite.Stmt, c int) int { + s.BindInt64(c, page*limit) + s.BindInt64(c+1, limit) + return 2 + }) +} + +func (sql *sql) Order(order string) { + switch strings.ToLower(order) { + case "asc", "": + sql.Append(` ORDER BY "id" ASC`) + case "desc": + sql.Append(` ORDER BY "id" DESC`) + default: + panic(fmt.Errorf("invalid order: %v", order)) + } +} + +func (sql *sql) OrderPaginate(order string, page, limit int64) { + sql.Order(order) + sql.Paginate(page, limit) +} diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 index 5e29e7916d5e82d28417dcbe3916205cf448e871..17fc81f3d41eb8df1e60716c4fbc0edc57e938ec 100644 GIT binary patch literal 274432 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

    xKUG>>y0>(DX-?_J()7|5 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 V3_6j|nXjEYLj~z;_YP4Y^cSFdBSHWG delta 1943 zcmZ`)Z)_7~81LI_@4dTT?>1?B8)M#$wWEchn`8pPNj9i5WSbi#U3Bs6b~jc_J7|m0 z5MkY)Pnr$(CLSSim6-TRL?|*7OazS@6OG20C=m(K#26C)g!nfebqkmg}v|~?H+0)UvE2^aQ zTwIIi;!2oPtS_M%g&0`p$>!6;>ZqRBJDNi+URf~D+K`&m^J*%C-u2dUX+3vnER$4m z$Zs)F-<{A*ub$3j@`SB(S8r#tjo9=qQDSdb&z|Vx(f($xf=DNFsu@ivPY(bemp+(M z6WQ!RT{Au&0KxSMJ0zm?M<0v!M|-wKcPk_=p=Y^BL0$o+*hWxjpL5DEJ)-8ulDcvF zDX_+s&G^-tAf(TjbQqqv1$;sn@M~&t;@YC=s0Zf*pd^)*)k3B)}rqqi@srb ze>tXEvEhwh2SJS~K^~V9A>!XHO*0y&{HtwI%k(STvnDK5PX1r6xTai8~)^;5h4;er6BMSg+g#mUbzAv60))g|!5<24Pgq8(;@58%7!ID3~n1$gMjbCCw zX3Pz0oh000vM1WJo4rPRFcV1=VV_*ytBUKP)aFN&JjE}RpkQb&s>xI<2=+lej}m1^&} zApGO-FdJ^39i&4+hC*-5G`fR;>IUdWQK{ERGUnEoul~7dDxZ8VecMqhf6M>$clYLz zAdB9fZ**}XNTEk&gYFPbNY@k 0 { + syncHeight = math.MaxUint32 + } for i, dbChain := range dbChains { chain := Chains.m[*dbChain.ID] @@ -128,7 +150,7 @@ func loadChains() (syncHeight uint32, err error) { } chain.Chain = dbChain - chain.ChainStatus = ChainStatusTracked + syncHeight = min(syncHeight, chain.SyncHeight) if chain.NetworkID != flag.NetworkID { @@ -149,8 +171,31 @@ func loadChains() (syncHeight uint32, err error) { } } - Chains.m[*dbChain.ID] = chain + chain.ChainStatus = ChainStatusTracked + Chains.trackedIDs = append(Chains.trackedIDs, chain.ID) + if chain.Issuance.IsPopulated() { + chain.ChainStatus = ChainStatusIssued + Chains.issuedIDs = append(Chains.issuedIDs, chain.ID) + } + + Chains.m[*chain.ID] = chain + } + + // Open any whitelisted chains that do not already have databases. + for id, chain := range Chains.m { + if chain.IsIgnored() || chain.Chain != nil { + continue + } + if err = chain.OpenNewByChainID(c, &id); err != nil { + return + } + Chains.trackedIDs = append(Chains.trackedIDs, chain.ID) + if chain.IsIssued() { + Chains.issuedIDs = append(Chains.issuedIDs, chain.ID) + } + Chains.m[*chain.ID] = chain } + dbChains = nil // Prevent closing any chains from this list. return } diff --git a/engine/engine.go b/engine/engine.go index 257e76d..a60b713 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -35,8 +35,6 @@ import ( var ( log _log.Log c = flag.FactomClient - - NetID = factom.Mainnet() ) const ( @@ -63,8 +61,8 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { return } if dblock.Header.NetworkID != flag.NetworkID { - log.Errorf("invalid Factom Blockchain NetworkID: %v", - dblock.Header.NetworkID) + log.Errorf("invalid Factom Blockchain NetworkID: %v, expected: %v", + dblock.Header.NetworkID, flag.NetworkID) return } @@ -122,26 +120,25 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { return _done } -func engine(stop <-chan struct{}, done chan struct{}) { - // Ensure done is only closed exactly once. - var once sync.Once - exit := func() { once.Do(func() { close(done) }) } - defer exit() +const numWorkers = 8 - eblocks := make(chan factom.EBlock) - // Ensure all workers exit and state is closed when we exit. - defer close(eblocks) +func engine(stop <-chan struct{}, done chan struct{}) { + defer close(done) defer Chains.Close() // Launch workers + eblocks := make(chan factom.EBlock) + var once sync.Once + stopWorkers := func() { once.Do(func() { close(eblocks) }) } + defer stopWorkers() + var dblock factom.DBlock wg := &sync.WaitGroup{} - const numWorkers = 8 launchWorkers(numWorkers, func() { for eb := range eblocks { // Read until close(eblocks) if err := Process(dblock.KeyMR, eb); err != nil { log.Errorf("ChainID(%v): %v", eb.ChainID, err) - exit() // Tell engine() to exit. + stopWorkers() // Tell workers and engine() to exit. } wg.Done() } @@ -164,6 +161,7 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Process all new DBlocks sequentially... for h := syncHeight + 1; h <= factomHeight; h++ { // Get DBlock. + dblock = factom.DBlock{} dblock.Header.Height = h if err := dblock.Get(c); err != nil { log.Errorf("%#v.Get(c): %v", dblock, err) @@ -179,7 +177,7 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Check for process errors... select { - case <-done: + case <-eblocks: // We cannot consider this DBlock completed. // Sync height will not be updated for all chains. return @@ -200,7 +198,8 @@ func engine(stop <-chan struct{}, done chan struct{}) { } if flag.LogDebug && h%100 == 0 { - log.Debugf("Synced to block %v...", h) + log.Debugf("Synced to block Height: %v KeyMR: %v", + h, dblock.KeyMR) } } diff --git a/engine/process.go b/engine/process.go index 8474223..387e295 100644 --- a/engine/process.go +++ b/engine/process.go @@ -23,13 +23,7 @@ package engine import ( - "fmt" - - jrpc "github.com/AdamSLevy/jsonrpc2/v11" - - "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" ) @@ -45,57 +39,28 @@ func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { Chains.ignore(eb.ChainID) return nil } - // Load this Entry Block. - if err := eb.Get(c); err != nil { - return fmt.Errorf("%#v.Get(c): %v", eb, err) - } - if !eb.IsFirst() { - Chains.ignore(eb.ChainID) - return nil - } - // Load first entry of new chain. - first := eb.Entries[0] - if err := first.Get(c); err != nil { - return fmt.Errorf("%#v.Get(c): %v", first, err) + var err error + chain, err = OpenNew(c, dbKeyMR, eb) + if err != nil { + return err } - - // Ignore chains with NameIDs that don't match the fat pattern. - nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { + if chain.IsUnknown() { Chains.ignore(eb.ChainID) return nil } - - var identity factom.Identity - _, identity.ChainID = fat.TokenIssuer(nameIDs) - if err := identity.Get(c); err != nil { - // A jrpc.Error indicates that the identity chain - // doesn't yet exist, which we tolerate. - if _, ok := err.(jrpc.Error); !ok { - return err - } - } - - if err := eb.GetEntries(c); err != nil { - return fmt.Errorf("%#v.GetEntries(c): %v", eb, err) + if err := chain.Sync(c); err != nil { + return err } - - var err error - chain.Chain, err = db.OpenNew(dbKeyMR, eb, flag.NetworkID, - identity) - if err != nil { - return fmt.Errorf("db.OpenNew(): %v") - } - if chain.Issuance.IsPopulated() { - chain.ChainStatus = ChainStatusIssued - } else { - chain.ChainStatus = ChainStatusTracked - } - Chains.set(chain.ID, chain) + Chains.set(chain.ID, chain, ChainStatusUnknown) return nil } if eb.Height <= chain.Head.Height { return nil } - return chain.Apply(c, dbKeyMR, eb) + prevStatus := chain.ChainStatus + if err := chain.Apply(c, dbKeyMR, eb); err != nil { + return err + } + Chains.set(chain.ID, chain, prevStatus) + return nil } diff --git a/factom/eblock.go b/factom/eblock.go index 42e1740..a8729a0 100644 --- a/factom/eblock.go +++ b/factom/eblock.go @@ -179,14 +179,7 @@ func (eb EBlock) Prev() EBlock { // If you are only interested in obtaining the first entry block in eb's chain, // and not all of the intermediary ones, then use GetFirst. func (eb EBlock) GetPrevAll(c *Client) ([]EBlock, error) { - ebs := []EBlock{} - for ; !eb.IsFirst(); eb = eb.Prev() { - if err := eb.Get(c); err != nil { - return nil, err - } - ebs = append(ebs, eb) - } - return ebs, nil + return eb.GetPrevUpTo(c, Bytes32{}) } // GetPrevUpTo returns a slice of all preceding EBlocks, in order from eb up @@ -197,24 +190,27 @@ func (eb EBlock) GetPrevAll(c *Client) ([]EBlock, error) { // returned with the fully populated []EBlock. Like Get, if eb does not have a // KeyMR, the chain head KeyMR is queried first. func (eb EBlock) GetPrevUpTo(c *Client, keyMR Bytes32) ([]EBlock, error) { - ebs := []EBlock{} if err := eb.Get(c); err != nil { return nil, err } if *eb.KeyMR == keyMR { return nil, nil } - for ; *eb.PrevKeyMR != keyMR && !eb.PrevKeyMR.IsZero(); eb = eb.Prev() { + ebs := []EBlock{eb} + for { + if *eb.PrevKeyMR == keyMR { + return ebs, nil + } + if eb.IsFirst() { + return ebs, fmt.Errorf("EBlock{%v} not found prior to EBlock{%v}", + keyMR, eb.KeyMR) + } + eb = eb.Prev() if err := eb.Get(c); err != nil { return nil, err } ebs = append(ebs, eb) } - if *ebs[len(ebs)-1].PrevKeyMR != keyMR { - return ebs, fmt.Errorf("EBlock{%v} not found prior to EBlock{%v}", - keyMR, eb.KeyMR) - } - return ebs, nil } // GetFirst finds the first Entry Block in eb's chain, and populates eb as @@ -277,12 +273,8 @@ func (eb *EBlock) UnmarshalBinary(data []byte) error { return fmt.Errorf("invalid length") } - // When the eb.ChainID is already populated, just reuse the data. - if eb.ChainID == nil { - eb.ChainID = new(Bytes32) - copy(eb.ChainID[:], data[:len(eb.ChainID)]) - } - i := len(eb.ChainID) + eb.ChainID = new(Bytes32) + i := copy(eb.ChainID[:], data[:len(eb.ChainID)]) eb.BodyMR = new(Bytes32) i += copy(eb.BodyMR[:], data[i:i+len(eb.BodyMR)]) eb.PrevKeyMR = new(Bytes32) diff --git a/factom/entry.go b/factom/entry.go index ad37a5c..e575cad 100644 --- a/factom/entry.go +++ b/factom/entry.go @@ -344,6 +344,7 @@ func (e *Entry) MarshalBinary() ([]byte, error) { return nil, fmt.Errorf("Entry cannot be larger than 10KB") } if e.ChainID == nil { + // We assume this is a chain creation entry. e.ChainID = new(Bytes32) *e.ChainID = ChainID(e.ExtIDs) } @@ -405,12 +406,8 @@ func (e *Entry) UnmarshalBinary(data []byte) error { return fmt.Errorf("invalid version byte") } i := 1 - // When the e.ChainID is already populated, just reuse the data. - if e.ChainID == nil { - e.ChainID = new(Bytes32) - copy(e.ChainID[:], data[i:i+len(e.ChainID)]) - } - i += len(e.ChainID) + e.ChainID = new(Bytes32) + i += copy(e.ChainID[:], data[i:i+len(e.ChainID)]) extIDTotalLen := int(binary.BigEndian.Uint16(data[33:35])) if extIDTotalLen == 1 || EntryHeaderLen+extIDTotalLen > len(data) { return fmt.Errorf("invalid ExtIDs length") diff --git a/flag/flag.go b/flag/flag.go index 8e5c7fd..288b8fb 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -276,6 +276,9 @@ func Parse() { if flagset["startscanheight"] { StartScanHeight = int32(startScanHeight) } + if !flagset["networkid"] { + NetworkID = factom.Mainnet() + } } func Validate() { diff --git a/srv/methods.go b/srv/methods.go index ccf43d7..ae5ac06 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -28,16 +28,14 @@ import ( "fmt" jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/gocraft/dbr" - "github.com/jinzhu/gorm" + "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/engine" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/Factom-Asset-Tokens/fatd/flag" - "github.com/Factom-Asset-Tokens/fatd/state" ) var c = flag.FactomClient @@ -84,7 +82,7 @@ func getIssuance(entry bool) jrpc.MethodFunc { return ResultGetIssuance{ ParamsToken: ParamsToken{ ChainID: chain.ID, - TokenID: chain.Token, + TokenID: chain.TokenID, IssuerChainID: chain.Identity.ChainID, }, Hash: chain.Issuance.Hash, @@ -108,13 +106,15 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { return err } - entry, err := chain.GetEntry(params.Hash) - if err == gorm.ErrRecordNotFound { - return ErrorTransactionNotFound - } + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + entry, err := db.SelectEntryByHashValid(conn, params.Hash) if err != nil { panic(err) } + if !entry.IsPopulated() { + return ErrorTransactionNotFound + } if getEntry { return entry @@ -160,14 +160,15 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { return err } + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + // Lookup Txs - entries, err := chain.GetEntries(params.StartHash, - params.Addresses, params.NFTokenID, + nfTkns, _ := fat1.NewNFTokens(params.NFTokenID) + entries, err := db.SelectEntryByAddress(conn, params.StartHash, + params.Addresses, nfTkns, params.ToFrom, params.Order, - params.Page, params.Limit) - if err == dbr.ErrNotFound { - return ErrorTransactionNotFound - } + int64(params.Page), int64(params.Limit)) if err != nil { panic(err) } @@ -222,11 +223,13 @@ func getBalance(data json.RawMessage) interface{} { return err } - adr, err := chain.GetAddress(params.Address) + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) } - return adr.Balance + return balance } type ResultGetBalances map[factom.Bytes32]uint64 @@ -260,16 +263,18 @@ func getBalances(data json.RawMessage) interface{} { return err } - issuedIDs := state.Chains.GetIssued() + issuedIDs := engine.Chains.GetIssued() balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { - chain := state.Chains.Get(&chainID) - adr, err := chain.GetAddress(params.Address) + chain := engine.Chains.Get(chainID) + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) } - if adr.Balance > 0 { - balances[chainID] = adr.Balance + if balance > 0 { + balances[*chainID] = balance } } return balances @@ -288,8 +293,10 @@ func getNFBalance(data json.RawMessage) interface{} { return err } - tkns, err := chain.GetNFTokensForOwner(params.Address, - params.Page, params.Limit, params.Order) + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + tkns, err := db.SelectNFTokensByOwner(conn, params.Address, + int64(params.Page), int64(params.Limit), params.Order) if err != nil { panic(err) } @@ -308,9 +315,10 @@ type ResultGetStats struct { Issuance *fat.Issuance CirculatingSupply uint64 `json:"circulating"` Burned uint64 `json:"burned"` - Transactions int `json:"transactions"` + Transactions int64 `json:"transactions"` IssuanceTimestamp int64 `json:"issuancets"` LastTransactionTimestamp int64 `json:"lasttxts,omitempty"` + NonZeroBalances int64 `json:"nonzerobalances, omitempty"` } var coinbaseRCDHash = fat.Coinbase() @@ -322,40 +330,46 @@ func getStats(data json.RawMessage) interface{} { return err } - coinbase, err := chain.GetAddress(&coinbaseRCDHash) + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + burned, err := db.SelectAddressBalance(conn, &coinbaseRCDHash) if err != nil { panic(err) } - burned := coinbase.Balance - txs, err := chain.GetEntries(nil, nil, nil, "", "", 0, 0) + txCount, err := db.SelectEntryCount(conn, true) + e, err := db.SelectEntryLatestValid(conn) if err != nil { panic(err) } - var lastTxTs int64 - if len(txs) > 0 { - lastTxTs = txs[len(txs)-1].Timestamp.Unix() + nonZeroBalances, err := db.SelectAddressCount(conn, true) + if err != nil { + panic(err) } + res := ResultGetStats{ - CirculatingSupply: chain.Issued - burned, + CirculatingSupply: chain.NumIssued - burned, Burned: burned, - Transactions: len(txs), + Transactions: txCount, IssuanceTimestamp: chain.Issuance.Timestamp.Unix(), - LastTransactionTimestamp: lastTxTs, + LastTransactionTimestamp: e.Timestamp.Unix(), + NonZeroBalances: nonZeroBalances, } if chain.IsIssued() { res.Issuance = &chain.Issuance } res.ChainID = chain.ID - res.TokenID = chain.Token - res.IssuerChainID = chain.Issuer + res.TokenID = chain.TokenID + res.IssuerChainID = chain.Identity.ChainID return res } type ResultGetNFToken struct { - NFTokenID fat1.NFTokenID `json:"id"` - Owner *factom.FAAddress `json:"owner"` - Metadata json.RawMessage `json:"metadata,omitempty"` + NFTokenID fat1.NFTokenID `json:"id"` + Owner *factom.FAAddress `json:"owner,omitempty"` + Burned bool `json:"burned,omitempty"` + Metadata json.RawMessage `json:"metadata,omitempty"` + CreationTx *factom.Bytes32 `json:"creationtx"` } func getNFToken(data json.RawMessage) interface{} { @@ -371,20 +385,31 @@ func getNFToken(data json.RawMessage) interface{} { return err } - tkn := state.NFToken{NFTokenID: *params.NFTokenID} - if err := chain.GetNFToken(&tkn); err != nil { - if err == gorm.ErrRecordNotFound { - err := ErrorTokenNotFound - err.Data = "No such NFTokenID has been issued" - return err - } + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + + owner, creationHash, metadata, err := db.SelectNFToken(conn, *params.NFTokenID) + if err != nil { panic(err) } - return ResultGetNFToken{ - NFTokenID: tkn.NFTokenID, - Metadata: tkn.Metadata, - Owner: tkn.Owner.RCDHash, + if creationHash.IsZero() { + err := ErrorTokenNotFound + err.Data = "No such NFTokenID has been issued" + return err + } + + res := ResultGetNFToken{ + NFTokenID: *params.NFTokenID, + Metadata: metadata, + Owner: &owner, + CreationTx: &creationHash, + } + + if owner == fat.Coinbase() { + res.Owner = nil + res.Burned = true } + return res } func getNFTokens(data json.RawMessage) interface{} { @@ -400,168 +425,39 @@ func getNFTokens(data json.RawMessage) interface{} { return err } - tkns, err := chain.GetAllNFTokens(params.Page, params.Limit, params.Order) + conn := chain.Pool.Get(nil) + defer chain.Put(conn) + + tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(conn, + params.Order, int64(params.Page), int64(params.Limit)) if err != nil { panic(err) } res := make([]ResultGetNFToken, len(tkns)) - for i, tkn := range tkns { - res[i].NFTokenID = tkn.NFTokenID - res[i].Metadata = tkn.Metadata - res[i].Owner = tkn.Owner.RCDHash + for i := range res { + res[i].NFTokenID = tkns[i] + res[i].Metadata = metadata[i] + res[i].CreationTx = &creationHashes[i] + res[i].Owner = &owners[i] + if owners[i] == fat.Coinbase() { + res[i].Owner = nil + res[i].Burned = true + } } return res } func sendTransaction(data json.RawMessage) interface{} { - if factom.Bytes32(flag.EsAdr).IsZero() { - return ErrorNoEC - } - params := ParamsSendTransaction{} - chain, err := validate(data, ¶ms) - if err != nil { - return err - } - - entry := params.Entry() - hash, _ := entry.ComputeHash() - transaction, err := chain.GetEntry(&hash) - if transaction.IsPopulated() { - err := ErrorInvalidTransaction - err.Data = "duplicate transaction" - return err - } - if err != gorm.ErrRecordNotFound { - panic(err) - } - - switch chain.Type { - case fat0.Type: - if err := validFAT0Transaction(chain, entry); err != nil { - return err - } - case fat1.Type: - if err := validFAT1Transaction(chain, entry); err != nil { - return err - } - default: - panic("invalid FAT type") - } - - balance, err := flag.ECAdr.GetBalance(c) - if err != nil { - panic(err) - } - cost, err := entry.Cost() - if err != nil { - rerr := ErrorInvalidTransaction - rerr.Data = err - return rerr - } - if balance < uint64(cost) { - return ErrorNoEC - } - txID, err := entry.ComposeCreate(c, flag.EsAdr) - if err != nil { - log.Error(err) - panic(err) - } - - return struct { - ChainID *factom.Bytes32 `json:"chainid"` - TxID *factom.Bytes32 `json:"txid"` - Hash *factom.Bytes32 `json:"entryhash"` - }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} + return nil } -func validFAT0Transaction(chain *state.Chain, entry factom.Entry) error { - tx := fat0.NewTransaction(entry) - rpcErr := ErrorInvalidTransaction - if err := tx.Validate(chain.ID1); err != nil { - rpcErr.Data = err.Error() - return rpcErr - } - - // check balances - if tx.IsCoinbase() { - if tx.Inputs.Sum() > uint64(chain.Supply)-chain.Issued { - rpcErr.Data = "insufficient coinbase supply" - return rpcErr - } - return nil - } - for rcdHash, amount := range tx.Inputs { - adr, err := chain.GetAddress(&rcdHash) - if err != nil { - log.Error(err) - panic(err) - } - if amount > adr.Balance { - rpcErr.Data = fmt.Sprintf("insufficient balance: %v", rcdHash) - return rpcErr - } - } +func validFAT0Transaction(chain *engine.Chain, entry factom.Entry) error { return nil } -func validFAT1Transaction(chain *state.Chain, entry factom.Entry) error { - tx := fat1.NewTransaction(entry) - rpcErr := ErrorInvalidTransaction - if err := tx.Validate(chain.ID1); err != nil { - rpcErr.Data = err.Error() - return rpcErr - } - - for rcdHash, tkns := range tx.Inputs { - adr, err := chain.GetAddress(&rcdHash) - if err != nil { - log.Error(err) - panic(err) - } - if tx.IsCoinbase() { - if chain.Supply > 0 && - uint64(chain.Supply)-chain.Issued < uint64(len(tkns)) { - // insufficient coinbase supply - rpcErr.Data = "insufficient coinbase supply" - return rpcErr - } - for tknID := range tkns { - tkn := state.NFToken{NFTokenID: tknID} - err := chain.GetNFToken(&tkn) - if err == nil { - rpcErr.Data = fmt.Sprintf( - "NFTokenID(%v) already exists", tknID) - return rpcErr - } - if err != gorm.ErrRecordNotFound { - log.Error(err) - panic(err) - } - } - break - } - if adr.Balance < uint64(len(tkns)) { - rpcErr.Data = fmt.Sprintf("insufficient balance: %v", rcdHash) - return rpcErr - } - for tknID := range tkns { - tkn := state.NFToken{NFTokenID: tknID, OwnerID: adr.ID} - err := chain.GetNFToken(&tkn) - if err == gorm.ErrRecordNotFound { - rpcErr.Data = fmt.Sprintf( - "NFTokenID(%v) is not owned by %v", - tknID, rcdHash) - return rpcErr - } - if err != nil { - log.Error(err) - panic(err) - } - } - } - +func validFAT1Transaction(chain *engine.Chain, entry factom.Entry) error { return nil } @@ -570,14 +466,14 @@ func getDaemonTokens(data json.RawMessage) interface{} { return err } - issuedIDs := state.Chains.GetIssued() + issuedIDs := engine.Chains.GetIssued() chains := make([]ParamsToken, len(issuedIDs)) for i, chainID := range issuedIDs { - chain := state.Chains.Get(&chainID) + chain := engine.Chains.Get(chainID) chainID := chainID - chains[i].ChainID = &chainID - chains[i].TokenID = chain.Token - chains[i].IssuerChainID = chain.Issuer + chains[i].ChainID = chainID + chains[i].TokenID = chain.TokenID + chains[i].IssuerChainID = chain.Identity.ChainID } return chains } @@ -604,7 +500,7 @@ func getSyncStatus(data json.RawMessage) interface{} { return ResultGetSyncStatus{Sync: sync, Current: current} } -func validate(data json.RawMessage, params Params) (*state.Chain, error) { +func validate(data json.RawMessage, params Params) (*engine.Chain, error) { if params == nil { if len(data) > 0 { return nil, jrpc.InvalidParams(`no "params" accepted`) @@ -622,7 +518,7 @@ func validate(data json.RawMessage, params Params) (*state.Chain, error) { } chainID := params.ValidChainID() if chainID != nil { - chain := state.Chains.Get(chainID) + chain := engine.Chains.Get(chainID) if !chain.IsIssued() { return nil, ErrorTokenNotFound } diff --git a/state/chain.go b/state/chain.go deleted file mode 100644 index 4325adc..0000000 --- a/state/chain.go +++ /dev/null @@ -1,77 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state - -import ( - "fmt" - - "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/gocraft/dbr" - "github.com/jinzhu/gorm" -) - -type Chain struct { - ID *factom.Bytes32 - ChainStatus - factom.Identity - fat.Issuance - Metadata - *gorm.DB - DBR *dbr.Connection -} - -func (chain Chain) String() string { - return fmt.Sprintf("{ChainStatus:%v, ID:%v, Metadata:%+v, "+ - "fat.Identity:%+v, fat.Issuance:%+v}", - chain.ChainStatus, chain.ID, chain.Metadata, - chain.Identity, chain.Issuance) -} - -func (chain *Chain) ignore() { - chain.ID = nil - chain.ChainStatus = ChainStatusIgnored -} -func (chain *Chain) track(height uint32, first factom.Entry) error { - chain.ChainStatus = ChainStatusTracked - chain.Identity.ChainID = factom.NewBytes32(first.ExtIDs[3]) - chain.Metadata.Token = string(first.ExtIDs[1]) - chain.Metadata.Issuer = chain.Identity.ChainID - chain.Metadata.Height = height - - if err := chain.setupDB(); err != nil { - return err - } - log.Debugf("Tracked: %v", chain) - return nil -} -func (chain *Chain) issue(issuance fat.Issuance) error { - chain.ChainStatus = ChainStatusIssued - chain.Issuance = issuance - - if err := chain.saveIssuance(); err != nil { - return err - } - log.Debugf("Issued: %v", chain) - return nil -} diff --git a/state/chainmap.go b/state/chainmap.go deleted file mode 100644 index c7c82ed..0000000 --- a/state/chainmap.go +++ /dev/null @@ -1,70 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state - -import ( - "sync" - - "github.com/Factom-Asset-Tokens/fatd/factom" -) - -var ( - Chains = ChainMap{m: map[factom.Bytes32]Chain{ - factom.Bytes32{31: 0x0a}: Chain{ChainStatus: ChainStatusIgnored}, - factom.Bytes32{31: 0x0c}: Chain{ChainStatus: ChainStatusIgnored}, - factom.Bytes32{31: 0x0f}: Chain{ChainStatus: ChainStatusIgnored}, - }, RWMutex: &sync.RWMutex{}} -) - -type ChainMap struct { - m map[factom.Bytes32]Chain - ids []factom.Bytes32 - *sync.RWMutex -} - -func (cm *ChainMap) set(id *factom.Bytes32, chain *Chain) { - defer cm.Unlock() - cm.Lock() - if chain.IsIssued() { - if chain, ok := cm.m[*id]; !ok || !chain.IsIssued() { - cm.ids = append(cm.ids, *id) - } - } - cm.m[*id] = *chain -} - -func (cm ChainMap) Get(id *factom.Bytes32) Chain { - defer cm.RUnlock() - cm.RLock() - chain, ok := cm.m[*id] - if !ok { - chain.ID = id - } - return chain -} - -func (cm ChainMap) GetIssued() []factom.Bytes32 { - defer cm.RUnlock() - cm.RLock() - return cm.ids -} diff --git a/state/chainstatus.go b/state/chainstatus.go deleted file mode 100644 index c137150..0000000 --- a/state/chainstatus.go +++ /dev/null @@ -1,58 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state - -type ChainStatus uint - -const ( - ChainStatusUnknown ChainStatus = 0 - ChainStatusTracked ChainStatus = 1 - ChainStatusIssued ChainStatus = 3 - ChainStatusIgnored ChainStatus = 4 -) - -func (status ChainStatus) IsUnknown() bool { - return status == ChainStatusUnknown -} -func (status ChainStatus) IsIgnored() bool { - return status == ChainStatusIgnored -} -func (status ChainStatus) IsTracked() bool { - return status&ChainStatusTracked == ChainStatusTracked -} -func (status ChainStatus) IsIssued() bool { - return status&ChainStatusIssued == ChainStatusIssued -} - -func (status ChainStatus) String() string { - s := "Unknown" - switch status { - case ChainStatusTracked: - s = "Tracked" - case ChainStatusIssued: - s = "Issued" - case ChainStatusIgnored: - s = "Ignored" - } - return s -} diff --git a/state/chainstatus_test.go b/state/chainstatus_test.go deleted file mode 100644 index f0a985b..0000000 --- a/state/chainstatus_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state_test - -import ( - "testing" - - . "github.com/Factom-Asset-Tokens/fatd/state" - "github.com/stretchr/testify/assert" -) - -const ( - unknownID int = iota - trackedID - issuedID - ignoredID -) - -var chainStatusTests = []struct { - ChainStatus - expected [4]bool -}{{ - ChainStatus: ChainStatusUnknown, - expected: [4]bool{unknownID: true}, -}, { - ChainStatus: ChainStatusTracked, - expected: [4]bool{trackedID: true}, -}, { - ChainStatus: ChainStatusIssued, - expected: [4]bool{trackedID: true, issuedID: true}, -}, { - ChainStatus: ChainStatusIgnored, - expected: [4]bool{ignoredID: true}, -}} - -func TestChainStatus(t *testing.T) { - for _, test := range chainStatusTests { - status := test.ChainStatus - t.Run(status.String(), func(t *testing.T) { - assert := assert.New(t) - expected := test.expected - assert.Equalf(expected[unknownID], status.IsUnknown(), - "IsUnknown()") - assert.Equalf(expected[trackedID], status.IsTracked(), - "IsTracked()") - assert.Equalf(expected[issuedID], status.IsIssued(), - "IsIssued()") - assert.Equalf(expected[ignoredID], status.IsIgnored(), - "IsIgnored()") - }) - } -} diff --git a/state/db.go b/state/db.go deleted file mode 100644 index 836208c..0000000 --- a/state/db.go +++ /dev/null @@ -1,546 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state - -import ( - "database/sql" - "encoding/json" - "fmt" - "io/ioutil" - "math" - "os" - - "github.com/gocraft/dbr" - "github.com/gocraft/dbr/dialect" - "github.com/jinzhu/gorm" - //_ "github.com/jinzhu/gorm/dialects/sqlite" - - "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/Factom-Asset-Tokens/fatd/flag" - _log "github.com/Factom-Asset-Tokens/fatd/log" -) - -var ( - SavedHeight uint32 - log _log.Log - c = flag.FactomClient -) - -// Load state from all existing databases -func Load() error { - log = _log.New("pkg", "state") - // Try to create the database directory in case it doesn't already - // exist. - if err := os.Mkdir(flag.DBPath, 0755); err != nil && !os.IsExist(err) { - return fmt.Errorf("os.Mkdir(%#v)", flag.DBPath) - } - - minHeight := uint32(math.MaxUint32) - - // Scan through all files within the database directory. Ignore invalid - // file names. - files, err := ioutil.ReadDir(flag.DBPath) - if err != nil { - return fmt.Errorf("ioutil.ReadDir(%#v): %v", flag.DBPath, err) - } - for _, f := range files { - fname := f.Name() - chain := Chain{ChainStatus: ChainStatusTracked} - - if chain.ID = fnameToChainID(fname); chain.ID == nil { - continue - } - log.Debugf("loading chain: %v", chain.ID) - var err error - if err = chain.open(fname); err != nil { - return err - } - if err := chain.loadMetadata(); err != nil { - return err - } - if err := chain.loadIssuance(); err != nil { - return err - } - - Chains.set(chain.ID, &chain) - if chain.Metadata.Height == 0 { - continue - } - if chain.Metadata.Height < minHeight { - minHeight = chain.Metadata.Height - } - } - - if minHeight < math.MaxUint32 { // If a minimum was found... - SavedHeight = minHeight // use it, otherwise start at zero. - } - return nil -} -func fnameToChainID(fname string) *factom.Bytes32 { - if len(fname) != dbFileNameLen || - fname[dbFileNameLen-len(dbFileExtension):dbFileNameLen] != dbFileExtension { - return nil - } - var chainID factom.Bytes32 - if err := json.Unmarshal( - []byte(fmt.Sprintf("%#v", fname[0:64])), &chainID); err != nil { - return nil - } - return &chainID -} - -func Close() { - defer Chains.Unlock() - Chains.Lock() - for _, chain := range Chains.m { - if chain.DB == nil { - continue - } - if err := chain.Close(); err != nil { - log.Errorf(err.Error()) - } - } -} - -func SaveHeight(height uint32) error { - Chains.Lock() - defer Chains.Unlock() - - for _, chain := range Chains.m { - if !chain.IsTracked() || - chain.Metadata.Height >= height || - chain.DB.Error != nil { - continue - } - if err := chain.saveHeight(height); err != nil { - return err - } - Chains.m[*chain.ID] = chain - } - SavedHeight = height - return nil -} - -const ( - dbDriver = "sqlite3" - dbFileExtension = ".sqlite3" - dbFileNameLen = len(factom.Bytes32{})*2 + len(dbFileExtension) -) - -// open a database -func (c *Chain) open(fname string) error { - fpath := flag.DBPath + "/" + fname - db, err := gorm.Open(dbDriver, fpath) - if err != nil { - return err - } - // Ensure the db gets closed if there are any issues. - defer func() { - if err != nil { - db.Close() - } - }() - db.LogMode(false) - if err = autoMigrate(db); err != nil { - return err - } - c.DB = db - c.DBR = &dbr.Connection{ - DB: db.DB(), Dialect: dialect.SQLite3, - EventReceiver: &dbr.NullEventReceiver{}, - } - return nil -} -func autoMigrate(db *gorm.DB) error { - if err := deleteEmptyTables(db); err != nil { - return fmt.Errorf("deleteEmptyTables(): %v", err) - } - if err := db.AutoMigrate(&entry{}).Error; err != nil { - return fmt.Errorf("db.AutoMigrate(&Entry{}): %v", err) - } - if err := db.AutoMigrate(&Address{}).Error; err != nil { - return fmt.Errorf("db.AutoMigrate(&Address{}): %v", err) - } - if err := db.AutoMigrate(&Metadata{}).Error; err != nil { - return fmt.Errorf("db.AutoMigrate(&Metadata{}): %v", err) - } - if err := db.AutoMigrate(&NFToken{}).Error; err != nil { - return fmt.Errorf("db.AutoMigrate(&Metadata{}): %v", err) - } - return nil -} - -func deleteEmptyTables(db *gorm.DB) error { - var tables []struct{ Name string } - var selectQry = "SELECT name FROM sqlite_master " - qry := selectQry + "WHERE type = 'table';" - if err := db.Raw(qry).Find(&tables).Error; err != nil { - return fmt.Errorf("%#v: %v", qry, err) - } - for _, table := range tables { - table := table.Name - if table == "sqlite_sequence" { - continue - } - var count int - if err := db.Table(table).Count(&count).Error; err != nil { - return fmt.Errorf("db.Table(%v).Count(): %v", table, err) - } - if count > 0 { - continue - } - qry = fmt.Sprintf("DROP TABLE %v;", table) - if err := db.Exec(qry).Error; err != nil { - return fmt.Errorf("%#v: %v", qry, err) - } - var indexes []struct{ Name string } - qry = selectQry + "WHERE type = 'index' AND tbl_name = ?;" - if err := db.Raw(qry, table). - Scan(&indexes).Error; err != nil { - return fmt.Errorf("%#v: %v", qry, err) - } - for _, index := range indexes { - index := index.Name - qry = fmt.Sprintf("DROP INDEX %v;", index) - if err := db.Exec(qry, index).Error; err != nil { - return fmt.Errorf("%#v: %v", qry, err) - } - } - } - return nil -} - -// setupDB a database for a given token chain. -func (chain *Chain) setupDB() error { - fname := fmt.Sprintf("%v%v", chain.ID, dbFileExtension) - var err error - if err = chain.open(fname); err != nil { - return err - } - // Ensure the db gets closed if there are any issues. - defer func() { - if err != nil { - chain.Close() - chain.DB = nil - } - }() - if err := chain.Create(&chain.Metadata).Error; err != nil { - return err - } - coinbase := newAddress(fat.Coinbase()) - if err := chain.Create(&coinbase).Error; err != nil { - return err - } - return nil -} - -func (chain *Chain) loadMetadata() error { - var MetadataTableCount int - if err := chain.DB.Model(&Metadata{}). - Count(&MetadataTableCount).Error; err != nil { - return err - } - if MetadataTableCount != 1 { - return fmt.Errorf(`table "metadata" must have exactly one row`) - } - if err := chain.First(&chain.Metadata).Error; err != nil { - return err - } - if !fat.ValidTokenNameIDs(fat.NameIDs(chain.Token, chain.Issuer)) || - *chain.ID != fat.ChainID(chain.Token, chain.Issuer) { - return fmt.Errorf(`corrupted "metadata" table for chain %v`, chain.ID) - } - chain.Identity.ChainID = chain.Metadata.Issuer - return nil -} - -func (chain *Chain) loadIssuance() error { - e := entry{} - if err := chain.First(&e).Error; err != nil { - if err == gorm.ErrRecordNotFound { - return nil - } - return err - } - if !e.IsValid() { - return fmt.Errorf("corrupted entry hash") - } - chain.Issuance = fat.NewIssuance(e.Entry()) - if err := chain.Issuance.UnmarshalEntry(); err != nil { - return err - } - chain.ChainStatus = ChainStatusIssued - if err := chain.Identity.Get(c); err != nil { - return err - } - return nil -} - -func (chain *Chain) saveIssuance() error { - var entriesTableCount int - if err := chain.DB.Model(&entry{}).Count(&entriesTableCount).Error; err != nil { - return err - } - if entriesTableCount != 0 { - return fmt.Errorf(`table "entries" must be empty prior to issuance`) - } - - if _, err := chain.createEntry(chain.Issuance.Entry.Entry); err != nil { - return err - } - chain.ChainStatus = ChainStatusIssued - return nil -} -func (chain *Chain) saveMetadata() error { - if err := chain.Save(&chain.Metadata).Error; err != nil { - return err - } - return nil -} -func (chain *Chain) createEntry(fe factom.Entry) (*entry, error) { - e := newEntry(fe) - if !e.IsValid() { - return nil, fmt.Errorf("invalid hash: factom.Entry%+v", fe) - } - if err := chain.Where("hash = ?", e.Hash).First(&e).Error; err != - gorm.ErrRecordNotFound { - return nil, err - } - if err := chain.Create(&e).Error; err != nil { - return nil, err - } - return &e, nil -} - -func (chain *Chain) createNFToken(tknID fat1.NFTokenID, - metadata json.RawMessage) (*NFToken, error) { - tkn := NFToken{NFTokenID: tknID, Metadata: metadata} - if err := chain.Where("nf_token_id = ?", tknID).First(&tkn).Error; err != - gorm.ErrRecordNotFound { - return nil, err - } - if err := chain.Create(&tkn).Error; err != nil { - return nil, err - } - return &tkn, nil -} - -func (chain *Chain) saveHeight(height uint32) error { - chain.Metadata.Height = height - if err := chain.saveMetadata(); err != nil { - return err - } - return nil -} -func (chain Chain) GetAddress(rcdHash *factom.FAAddress) (Address, error) { - a := Address{RCDHash: rcdHash} - if err := chain.Where(&a).First(&a).Error; err != nil && - err != gorm.ErrRecordNotFound { - return a, err - } - return a, nil -} - -func (chain Chain) GetNFToken(tkn *NFToken) error { - qry := chain.Where("nf_token_id = ?", tkn.NFTokenID) - if tkn.OwnerID != 0 { - qry = chain.Where("nf_token_id = ? AND owner_id = ?", - tkn.NFTokenID, tkn.OwnerID) - } - if err := qry.Preload("Owner").First(tkn).Error; err != nil { - return err - } - return nil -} - -func (chain Chain) GetNFTokensForOwner(rcdHash *factom.FAAddress, - page, limit uint64, order string) (fat1.NFTokens, error) { - sess := chain.DBR.NewSession(nil) - ownerID := dbr.Select("id").From("addresses"). - Where("rcd_hash = ?", rcdHash) - stmt := sess.Select("nf_token_id").From("nf_tokens"). - Where("owner_id = ?", ownerID). - Paginate(page, limit) - - switch order { - case "", "asc": - stmt.OrderAsc("nf_token_id") - case "desc": - stmt.OrderDesc("nf_token_id") - default: - panic(fmt.Sprintf("invalid order value: %#v", order)) - } - - var dbtkns []NFToken - if _, err := stmt.Load(&dbtkns); err != nil { - return nil, err - } - tkns := make(fat1.NFTokens, len(dbtkns)) - for _, tkn := range dbtkns { - tkns[tkn.NFTokenID] = struct{}{} - } - return tkns, nil -} - -func (chain Chain) GetAllNFTokens(page, limit uint64, order string) ([]NFToken, error) { - var tkns []NFToken - if err := chain.Offset(page * limit).Limit(limit). - Order("nf_token_id " + order). - Preload("Owner").Find(&tkns).Error; err != nil { - return nil, err - } - return tkns, nil -} -func (chain *Chain) rollbackUnlessCommitted(savedChain Chain, err *error) { - // This rollback will silently fail if the db tx has already - // been committed. - rberr := chain.Rollback().Error - chain.DB = savedChain.DB - if rberr == sql.ErrTxDone { - // already committed - return - } - if rberr != nil && *err != nil { - // Report other Rollback errors if there wasn't already - // a returned error. - *err = rberr - return - } - // complete rollback - chain.Issued = savedChain.Issued -} - -func (chain Chain) GetEntry(hash *factom.Bytes32) (factom.Entry, error) { - e, err := chain.getEntry(hash) - if e == nil { - return factom.Entry{}, err - } - return e.Entry(), nil -} - -func (chain Chain) getEntry(hash *factom.Bytes32) (*entry, error) { - e := entry{} - if err := chain.Not("id = ?", 1). - Where("hash = ?", hash).First(&e).Error; err != nil { - return nil, err - } - e.Hash = hash - return &e, nil -} - -const LimitMax = 1000 - -func (chain Chain) GetEntries(hash *factom.Bytes32, - rcdHashes []factom.FAAddress, tknID *fat1.NFTokenID, - toFrom, order string, - page, limit uint64) ([]factom.Entry, error) { - if limit == 0 || limit > LimitMax { - limit = LimitMax - } - - sess := chain.DBR.NewSession(nil) - stmt := sess.Select("*").From("entries").Where("id != 1"). - Paginate(page, limit) - - var sign string - switch order { - case "", "asc": - stmt.OrderAsc("id") - sign = ">" - case "desc": - stmt.OrderDesc("id") - sign = "<" - default: - panic(fmt.Sprintf("invalid order value: %#v", order)) - } - - if hash != nil { - entryID := dbr.Select("id").From("entries").Where("hash = ?", hash) - stmt.Where(fmt.Sprintf("id %v= ?", sign), entryID) - } - - if len(rcdHashes) > 0 { - addressIDs := dbr.Select("id").From("addresses"). - Where("rcd_hash IN ?", rcdHashes) - var entryIDs dbr.Builder - switch toFrom { - case "to", "from": - entryIDs = dbr.Select("entry_id"). - From("address_transactions_"+toFrom). - Where("address_id IN ?", addressIDs) - case "": - entryIDs = dbr.UnionAll( - dbr.Select("entry_id").From("address_transactions_to"). - Where("address_id IN ?", addressIDs), - dbr.Select("entry_id").From("address_transactions_from"). - Where("address_id IN ?", addressIDs)) - default: - panic(fmt.Sprintf("invalid toFrom value: %#v", toFrom)) - } - stmt.Where("id IN ?", entryIDs) - } - - if tknID != nil { - tokenIDStmt := dbr.Select("id").From("nf_tokens"). - Where("nf_token_id == ?", tknID) - entryIDs := dbr.Select("entry_id"). - From("nf_token_transactions"). - Where("nf_token_id == ?", tokenIDStmt) - stmt.Where("id IN ?", entryIDs) - } - - var es []entry - if _, err := stmt.Load(&es); err != nil { - return nil, err - } - entries := make([]factom.Entry, len(es)) - for i, e := range es { - entries[i] = e.Entry() - } - return entries, nil -} - -type erlog struct{} - -func (e erlog) Event(eventName string) { - log.Debugf("Event: %#v", eventName) -} -func (e erlog) EventKv(eventName string, kvs map[string]string) { - log.Debugf("Event: %#v Kv: %v", eventName, kvs) -} -func (e erlog) EventErr(eventName string, err error) error { - log.Debugf("Event: %#v Err: %v", eventName, err) - return err -} -func (e erlog) EventErrKv(eventName string, err error, kvs map[string]string) error { - log.Debugf("Event: %#v Err: %v Kvs: %v", eventName, err, kvs) - return err -} -func (e erlog) Timing(eventName string, nanoseconds int64) { - log.Debugf("Event: %#v Timing: %v", eventName, nanoseconds) -} -func (e erlog) TimingKv(eventName string, nanoseconds int64, kvs map[string]string) { - log.Debugf("Event: %#v Timing: %v Kvs: %v", eventName, nanoseconds, kvs) -} diff --git a/state/doc.go b/state/doc.go deleted file mode 100644 index 8986e11..0000000 --- a/state/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state diff --git a/state/process.go b/state/process.go deleted file mode 100644 index 7021c39..0000000 --- a/state/process.go +++ /dev/null @@ -1,367 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state - -import ( - "fmt" - - jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/jinzhu/gorm" - - "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" -) - -func Process(eb factom.EBlock) error { - // Skip ignored chains or EBlocks for heights earlier than this chain's - // state. - chain := Chains.Get(eb.ChainID) - if chain.IsIgnored() || eb.Height <= chain.Metadata.Height { - return nil - } - return chain.Process(eb) -} - -func (chain *Chain) Process(eb factom.EBlock) error { - // Ensure changes to chain are saved in Chains. - defer Chains.set(eb.ChainID, chain) - - // Load this Entry Block. - if err := eb.Get(c); err != nil { - return fmt.Errorf("%#v.Get(c): %v", eb, err) - } - - // Check if the EBlock represents a new chain. - if eb.IsFirst() { - // Load first entry of new chain. - first := eb.Entries[0] - if err := first.Get(c); err != nil { - return fmt.Errorf("%#v.Get(c): %v", first, err) - } - - // Ignore chains with NameIDs that don't match the fat pattern. - if !fat.ValidTokenNameIDs(first.ExtIDs) { - chain.ignore() - return nil - } - - // Track this chain going forward. - if err := chain.track(eb.Height, first); err != nil { - return err - } - if len(eb.Entries) == 1 { - return nil - } - // The first entry cannot be a valid Issuance entry, so discard - // it and process the rest. - eb.Entries = eb.Entries[1:] - } else if !chain.IsTracked() { - // Ignore chains that are not already tracked. - chain.ignore() - return nil - } - - return chain.process(eb) -} - -func (chain *Chain) process(eb factom.EBlock) (err error) { - defer func() { - if err != nil { - return - } - chain.saveHeight(eb.Height) - }() - if !chain.IsIssued() { - return chain.processIssuance(eb) - } - return chain.processTransactions(eb.Entries) -} - -// In general the following checks are ordered from cheapest to most expensive -// in terms of computation and memory. -func (chain *Chain) processIssuance(eb factom.EBlock) error { - if !chain.Identity.IsPopulated() { - // The Identity may not have existed when this chain was first tracked. - // Attempt to retrieve it. - if err := chain.Identity.Get(c); err != nil { - if _, ok := err.(jrpc.Error); ok { - return nil - } - return err - } - } - // If these entries were created in a lower block height than the - // Identity entry, then none of them can be a valid Issuance entry. - if eb.Height < chain.Identity.Height { - return nil - } - - for i, e := range eb.Entries { - // If this entry was created before the Identity entry then it - // can't be valid. - if e.Timestamp.Before(chain.Identity.Timestamp) { - log.Debugf("Invalid Issuance Entry: %v, %v", e.Hash, - "created before identity") - continue - } - // Get the data for the entry. - if err := e.Get(c); err != nil { - return fmt.Errorf("Entry%+v.Get(c): %v", e, err) - } - issuance := fat.NewIssuance(e) - if err := issuance.Validate(&chain.Identity.ID1); err != nil { - log.Debugf("Invalid Issuance Entry: %v, %v", e.Hash, err) - continue - } - - if err := chain.issue(issuance); err != nil { - return err - } - - // Process remaining entries as transactions - return chain.processTransactions(eb.Entries[i+1:]) - } - return nil -} - -func (chain *Chain) processTransactions(es []factom.Entry) error { - for _, e := range es { - if err := e.Get(c); err != nil { - return fmt.Errorf("Entry%v.Get(c): %v", e, err) - } - switch chain.Type { - case fat0.Type: - transaction := fat0.NewTransaction(e) - if err := transaction.Validate(chain.Identity.ID1); err != nil { - log.Debugf("Invalid Transaction Entry: %v, %v", - e.Hash, err) - continue - } - if err := chain.applyFAT0(transaction); err != nil { - return err - } - case fat1.Type: - transaction := fat1.NewTransaction(e) - if err := transaction.Validate(chain.Identity.ID1); err != nil { - log.Debugf("Invalid Transaction Entry: %v, %v", - e.Hash, err) - continue - } - if err := chain.applyFAT1(transaction); err != nil { - return err - } - } - } - return nil -} - -func (chain *Chain) applyFAT0(transaction fat0.Transaction) (err error) { - db := chain.Begin() - defer chain.rollbackUnlessCommitted(*chain, &err) - chain.DB = db - - entry, err := chain.createEntry(transaction.Entry.Entry) - if err != nil { - return err - } - if entry == nil { - // replayed transaction - log.Debugf("Invalid Transaction Entry: %v, replayed transaction", - transaction.Hash) - return nil - } - - for rcdHash, amount := range transaction.Inputs { - adr, err := chain.GetAddress(&rcdHash) - if err != nil { - return err - } - if err := chain.DB.Model(&adr).Association("From"). - Append(entry).Error; err != nil { - return err - } - if transaction.IsCoinbase() { - if chain.Supply > 0 && - uint64(chain.Supply)-chain.Issued < amount { - // insufficient coinbase supply - log.Debugf("Invalid Transaction Entry: %v, "+ - "insufficient coinbase supply", - entry.Hash) - return nil - } - chain.Issued += amount - if err := chain.saveMetadata(); err != nil { - return err - } - break - } - if adr.Balance < amount { - // insufficient balance - log.Debugf("Invalid Transaction Entry: %v, "+ - "insufficient balance: %v", - entry.Hash, adr.Address()) - return nil - } - adr.Balance -= amount - if err := chain.Save(&adr).Error; err != nil { - return err - } - } - - for rcdHash, amount := range transaction.Outputs { - a, err := chain.GetAddress(&rcdHash) - if err != nil { - return err - } - a.Balance += amount - if err := chain.Save(&a).Error; err != nil { - return err - } - if err := chain.DB.Model(&a).Association("To"). - Append(entry).Error; err != nil { - return err - } - } - log.Debugf("Valid Transaction Entry: %+v", transaction) - - return chain.Commit().Error -} - -func (chain *Chain) applyFAT1(transaction fat1.Transaction) (err error) { - db := chain.Begin() - defer chain.rollbackUnlessCommitted(*chain, &err) - chain.DB = db - - entry, err := chain.createEntry(transaction.Entry.Entry) - if err != nil { - return err - } - if entry == nil { - // replayed transaction - log.Debugf("Invalid Transaction Entry: %v, replayed transaction", - transaction.Hash) - return nil - } - - allTkns := make(map[fat1.NFTokenID]NFToken, transaction.Inputs.NumNFTokenIDs()) - for rcdHash, tkns := range transaction.Inputs { - adr, err := chain.GetAddress(&rcdHash) - if err != nil { - return err - } - if err := chain.DB.Model(&adr).Association("From"). - Append(entry).Error; err != nil { - return err - } - if transaction.IsCoinbase() { - if chain.Supply > 0 && - uint64(chain.Supply)-chain.Issued < uint64(len(tkns)) { - // insufficient coinbase supply - log.Debugf("Invalid Transaction Entry: %v, "+ - "insufficient coinbase supply", - entry.Hash) - return nil - } - chain.Issued += uint64(len(tkns)) - if err := chain.saveMetadata(); err != nil { - return err - } - for tknID := range tkns { - tkn, err := chain.createNFToken(tknID, - transaction.TokenMetadata[tknID]) - if err != nil { - return err - } - if tkn == nil { - log.Debugf("Invalid Transaction Entry: %v, "+ - "NFTokenID(%v) already exists", - entry.Hash, tknID) - return nil - } - allTkns[tknID] = *tkn - } - break - } - if adr.Balance < uint64(len(tkns)) { - // insufficient balance - log.Debugf("Invalid Transaction Entry: %v, "+ - "insufficient balance: %v", - entry.Hash, adr.Address()) - return nil - } - adr.Balance -= uint64(len(tkns)) - if err := chain.Save(&adr).Error; err != nil { - return err - } - for tknID := range tkns { - tkn := NFToken{NFTokenID: tknID, OwnerID: adr.ID} - err := chain.GetNFToken(&tkn) - if err == gorm.ErrRecordNotFound { - log.Debugf("Invalid Transaction Entry: %v, "+ - "NFTokenID(%v) is not owned by %v", - entry.Hash, tknID, rcdHash) - return nil - } - if err != nil { - return err - } - if err := chain.DB.Model(&tkn).Association("PreviousOwners"). - Append(&adr).Error; err != nil { - return err - } - allTkns[tknID] = tkn - } - } - - for rcdHash, tkns := range transaction.Outputs { - a, err := chain.GetAddress(&rcdHash) - if err != nil { - return err - } - a.Balance += uint64(len(tkns)) - if err := chain.Save(&a).Error; err != nil { - return err - } - if err := chain.DB.Model(&a).Association("To"). - Append(entry).Error; err != nil { - return err - } - for tknID := range tkns { - tkn := allTkns[tknID] - tkn.Owner = a - tkn.OwnerID = a.ID - if err := chain.Save(&tkn).Error; err != nil { - return err - } - if err := chain.DB.Model(&tkn).Association("Transactions"). - Append(entry).Error; err != nil { - return err - } - } - } - log.Debugf("Valid Transaction Entry: %T%+v", transaction, transaction) - - return chain.Commit().Error -} diff --git a/state/schema.go b/state/schema.go deleted file mode 100644 index c972e9f..0000000 --- a/state/schema.go +++ /dev/null @@ -1,97 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package state - -import ( - "time" - - "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/jinzhu/gorm" -) - -type Metadata struct { - gorm.Model - - Height uint32 - - Token string - Issuer *factom.Bytes32 - - Issued uint64 -} - -type entry struct { - ID uint64 - Hash *factom.Bytes32 `gorm:"type:VARCHAR(32); UNIQUE_INDEX; NOT NULL;"` - Timestamp time.Time `gorm:"NOT NULL;"` - Data factom.Bytes `gorm:"NOT NULL;"` -} - -func newEntry(e factom.Entry) entry { - b, _ := e.MarshalBinary() - return entry{ - Hash: e.Hash, - Timestamp: e.Timestamp, - Data: b, - } -} - -func (e entry) IsValid() bool { - return *e.Hash == factom.EntryHash(e.Data) -} - -func (e entry) Entry() factom.Entry { - fe := factom.Entry{Hash: e.Hash, Timestamp: e.Timestamp} - fe.UnmarshalBinary(e.Data) - return fe -} - -type Address struct { - gorm.Model - RCDHash *factom.FAAddress `gorm:"type:varchar(32); UNIQUE_INDEX; NOT NULL;"` - - Balance uint64 `gorm:"NOT NULL;"` - - To []entry `gorm:"many2many:address_transactions_to;"` - From []entry `gorm:"many2many:address_transactions_from;"` -} - -func newAddress(fa factom.FAAddress) Address { - return Address{RCDHash: &fa} -} - -func (a Address) Address() factom.FAAddress { - return *a.RCDHash -} - -type NFToken struct { - gorm.Model - NFTokenID fat1.NFTokenID `gorm:"UNIQUE_INDEX"` - Metadata []byte - OwnerID uint `gorm:"INDEX"` - Owner Address `gorm:"foreignkey:OwnerID"` - - PreviousOwners []Address `gorm:"many2many:nf_token_previousowners;"` - Transactions []entry `gorm:"many2many:nf_token_transactions;"` -} From b6d679bb3b2b01c3344c65290875456ca1fcf9c8 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 9 Aug 2019 14:15:57 -0800 Subject: [PATCH 013/124] fix(srv,engine): Sync height validation, get-transactions API Major fix: nf_token_transactions table omitted coinbase transactions, causing lookups to fail BREAKING CHANGE: Databases must be rebuilt --- cli/cmd/gettransactions.go | 18 ++++++++++++---- db/apply.go | 8 ++++---- db/entry.go | 42 +++++++++++++++++++++----------------- db/nftoken.go | 11 ++++++++-- db/sql.go | 42 +++++++++++++++++++++++++++----------- db/validate.go | 1 + engine/chainmap.go | 5 ++++- engine/engine.go | 40 ++++++++++++++++++------------------ factom/networkid.go | 22 ++++++++++++++------ flag/flag.go | 14 +++++++------ srv/methods.go | 22 +++++++++++++------- srv/params.go | 13 +++++++++--- 12 files changed, 154 insertions(+), 84 deletions(-) diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index e72ef07..48f8a39 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -37,9 +37,10 @@ import ( var ( paramsGetTxs = srv.ParamsGetTransactions{ - StartHash: new(factom.Bytes32), - NFTokenID: new(fat1.NFTokenID), - ParamsToken: srv.ParamsToken{ChainID: paramsToken.ChainID}, + StartHash: new(factom.Bytes32), + NFTokenID: new(fat1.NFTokenID), + ParamsToken: srv.ParamsToken{ChainID: paramsToken.ChainID}, + ParamsPagination: srv.ParamsPagination{Page: new(uint64)}, } to, from bool transactionIDs []factom.Bytes32 @@ -80,7 +81,7 @@ more, and in the case of a FAT-1 chain, by a single --nftokenid. Use --page and rootCmplCmd.Sub["help"].Sub["get"].Sub["transactions"] = complete.Command{} flags := cmd.Flags() - flags.Uint64VarP(¶msGetTxs.Page, "page", "p", 1, "Page of returned txs") + flags.Uint64VarP(paramsGetTxs.Page, "page", "p", 1, "Page of returned txs") flags.Uint64VarP(¶msGetTxs.Limit, "limit", "l", 10, "Limit of returned txs") flags.VarPF((*txOrder)(¶msGetTxs.Order), "order", "", "Order of returned txs"). DefValue = "asc" @@ -160,6 +161,15 @@ func validateGetTxsFlags(cmd *cobra.Command, args []string) error { if !flags.Changed("nftokenid") { paramsGetTxs.NFTokenID = nil } + if flags.Changed("page") { + if *paramsGetTxs.Page == 0 { + return fmt.Errorf("--page cannot be 0, starts at 1") + } + if *paramsGetTxs.Page == 1 { + // No need to explicitly send "page": 1 + paramsGetTxs.Page = nil + } + } return nil } diff --git a/db/apply.go b/db/apply.go index a592933..2473a48 100644 --- a/db/apply.go +++ b/db/apply.go @@ -240,6 +240,10 @@ func (chain *Chain) applyFAT1Tx( return } for nfID := range nfTkns { + if err = chain.insertNFTokenTransaction( + nfID, adrTxID); err != nil { + return + } metadata := tx.TokenMetadata[nfID] if len(metadata) == 0 { continue @@ -247,10 +251,6 @@ func (chain *Chain) applyFAT1Tx( if err = chain.setNFTokenMetadata(nfID, metadata); err != nil { return } - if err = chain.insertNFTokenTransaction( - nfID, adrTxID); err != nil { - return - } } err = chain.numIssuedAdd(addIssued) return diff --git a/db/entry.go b/db/entry.go index 3f5cf42..95cf66f 100644 --- a/db/entry.go +++ b/db/entry.go @@ -96,13 +96,16 @@ func SelectEntryCount(conn *sqlite.Conn, validOnly bool) (int64, error) { func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, adrs []factom.FAAddress, nfTkns fat1.NFTokens, toFrom, order string, - page, limit int64) ([]factom.Entry, error) { + page, limit uint64) ([]factom.Entry, error) { + if page == 0 { + return nil, fmt.Errorf("invalid page") + } var sql sql sql.Append(SelectEntryWhere + `"valid" = true`) if startHash != nil { sql.Append(` AND "id" >= (SELECT "id" FROM "entries" WHERE "hash" = ?)`, - func(s *sqlite.Stmt, c int) int { - s.BindBytes(c, startHash[:]) + func(s *sqlite.Stmt, p int) int { + s.BindBytes(p, startHash[:]) return 1 }) } @@ -110,39 +113,38 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, switch strings.ToLower(toFrom) { case "to": to = true - case "from": - case "": + case "from", "": default: panic(fmt.Errorf("invalid toFrom: %v", toFrom)) } if len(nfTkns) > 0 { sql.Append(` AND "id" IN ( SELECT "entry_id" FROM "nf_token_address_transactions" - WHERE "nf_token_id" IN (`) // 2 open ( - sql.Bind(len(nfTkns), func(s *sqlite.Stmt, c int) int { + WHERE "nf_tkn_id" IN (`) // 2 open ( + sql.Bind(len(nfTkns), func(s *sqlite.Stmt, p int) int { i := 0 for nfTkn := range nfTkns { - s.BindInt64(c+i, int64(nfTkn)) + s.BindInt64(p+i, int64(nfTkn)) i++ } return len(nfTkns) }) sql.Append(`)`) // 1 open ( if len(adrs) > 0 { - sql.Append(` AND "owner_id" IN ( + sql.Append(` AND "address_id" IN ( SELECT "id" FROM "addresses" WHERE "address" IN (`) // 3 open ( - sql.Bind(len(adrs), func(s *sqlite.Stmt, c int) int { + sql.Bind(len(adrs), func(s *sqlite.Stmt, p int) int { for i, adr := range adrs { - s.BindBytes(c+i, adr[:]) + s.BindBytes(p+i, adr[:]) } return len(adrs) }) sql.Append(`))`) // 1 open ( } if len(toFrom) > 0 { - sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, c int) int { - s.BindBool(c, to) + sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, p int) int { + s.BindBool(p, to) return 1 }) } @@ -150,18 +152,20 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, } else if len(adrs) > 0 { sql.Append(` AND "id" IN ( SELECT "entry_id" FROM "address_transactions" - WHERE "address" IN (?`) // 2 open ( + WHERE "address_id" IN ( + SELECT "id" FROM "addresses" + WHERE "address" IN (`) // 3 open ( - sql.Bind(len(adrs), func(s *sqlite.Stmt, c int) int { + sql.Bind(len(adrs), func(s *sqlite.Stmt, p int) int { for i, adr := range adrs { - s.BindBytes(c+i, adr[:]) + s.BindBytes(p+i, adr[:]) } return len(adrs) }) - sql.Append(`)`) // 1 open ( + sql.Append(`))`) // 1 open ( if len(toFrom) > 0 { - sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, c int) int { - s.BindBool(c, to) + sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, p int) int { + s.BindBool(p, to) return 1 }) } diff --git a/db/nftoken.go b/db/nftoken.go index ad575e8..fab50b8 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -2,6 +2,7 @@ package db import ( "encoding/json" + "fmt" "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/fatd/factom" @@ -71,8 +72,11 @@ func SelectNFToken(conn *sqlite.Conn, } func SelectNFTokens(conn *sqlite.Conn, - order string, page, limit int64) ([]fat1.NFTokenID, + order string, page, limit uint64) ([]fat1.NFTokenID, []factom.FAAddress, []factom.Bytes32, [][]byte, error) { + if page == 0 { + return nil, nil, nil, nil, fmt.Errorf("invalid page") + } stmt := conn.Prep(`SELECT "id", "owner", "creation_hash", "metadata" FROM "nf_tokens_addresses";`) defer stmt.Reset() @@ -113,7 +117,10 @@ func SelectNFTokens(conn *sqlite.Conn, } func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, - page, limit int64, order string) ([]fat1.NFTokenID, error) { + page, limit uint64, order string) ([]fat1.NFTokenID, error) { + if page == 0 { + return nil, fmt.Errorf("invalid page") + } var sql sql sql.Append(`SELECT "id" FROM "nf_tokens" WHERE "owner_id" = ( SELECT "id" FROM "addresses" WHERE "address" = ?)`, diff --git a/db/sql.go b/db/sql.go index 175f452..6eded52 100644 --- a/db/sql.go +++ b/db/sql.go @@ -7,27 +7,42 @@ import ( "crawshaw.io/sqlite" ) +// bindFunc is a function that binds one or more values to a sqlite.Stmt. The +// starting param index is passed into the bindFunc when sql.Prep is called. +// The bindFunc must return the number of binds it called on the Stmt so that +// the param index can be advanced. type bindFunc func(*sqlite.Stmt, int) int +// sql is a SQL builder for more complex queries. It allows for adding binds to +// a sqlite.Stmt before it is prepared. As SQL is appended to the query, +// bindFuncs are queued for later when sql.Prep() is called. Do not copy a +// non-zero sql. type sql struct { - sql string + sql strings.Builder binds []bindFunc } -func (sql sql) Prep(conn *sqlite.Conn) *sqlite.Stmt { - sql.sql += `;` - stmt := conn.Prep(sql.sql) - col := 1 +// Appends a trailing ';' to the SQL and calls conn.Prep. Finally all bindFuncs +// are called and the stmt is returned ready for its first Stmt.Step() call. +func (sql *sql) Prep(conn *sqlite.Conn) *sqlite.Stmt { + sql.sql.WriteString(`;`) + log.Debug(sql.sql.String()) + stmt := conn.Prep(sql.sql.String()) + param := 1 for _, bind := range sql.binds { - col += bind(stmt, col) + param += bind(stmt, param) } return stmt } +// Append str to the SQL and append the binds. func (sql *sql) Append(str string, binds ...bindFunc) { - sql.sql += str + sql.sql.WriteString(str) sql.binds = append(sql.binds, binds...) } + +// Append n comma separated params placeholders (e.g. "?, ?, ... , ?") to the +// SQL and append the binds. func (sql *sql) Bind(n int, binds ...bindFunc) { str := strings.TrimRight(strings.Repeat("?, ", n), ", ") sql.Append(str, binds...) @@ -35,17 +50,19 @@ func (sql *sql) Bind(n int, binds ...bindFunc) { const MaxLimit = 600 -func (sql *sql) Paginate(page, limit int64) { +// Append "LIMIT ?, ?" to the SQL and the appropriate page and limit binds. +func (sql *sql) Paginate(page, limit uint64) { if limit == 0 || limit > MaxLimit { limit = MaxLimit } - sql.Append(` LIMIT ?, ?`, func(s *sqlite.Stmt, c int) int { - s.BindInt64(c, page*limit) - s.BindInt64(c+1, limit) + sql.Append(` LIMIT ?, ?`, func(s *sqlite.Stmt, p int) int { + s.BindInt64(p, int64((page-1)*limit)) + s.BindInt64(p+1, int64(limit)) return 2 }) } +// Append "ORDER BY "id" ASC or DESC". No binds are added. func (sql *sql) Order(order string) { switch strings.ToLower(order) { case "asc", "": @@ -57,7 +74,8 @@ func (sql *sql) Order(order string) { } } -func (sql *sql) OrderPaginate(order string, page, limit int64) { +// Combines Order and Paginate in one call. +func (sql *sql) OrderPaginate(order string, page, limit uint64) { sql.Order(order) sql.Paginate(page, limit) } diff --git a/db/validate.go b/db/validate.go index f18a46f..0c064d4 100644 --- a/db/validate.go +++ b/db/validate.go @@ -16,6 +16,7 @@ import ( // continuity of all stored EBlocks and Entries. It does not validate the // validity of the saved DBlock KeyMRs. func (chain Chain) Validate() (err error) { + chain.Log.Debug("Validating database...") // Validate ChainID... read := chain.Pool.Get(nil) write := chain.Conn diff --git a/engine/chainmap.go b/engine/chainmap.go index f6c3407..7544ec5 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -47,7 +47,7 @@ type ChainMap struct { *sync.RWMutex } -func (cm ChainMap) set(id *factom.Bytes32, chain Chain, prevStatus ChainStatus) { +func (cm *ChainMap) set(id *factom.Bytes32, chain Chain, prevStatus ChainStatus) { defer cm.Unlock() cm.Lock() cm.m[*id] = chain @@ -113,6 +113,9 @@ func (cm ChainMap) Close() { } } +// loadChains loads all chains from the database that are not blacklisted, and +// syncs them. Any whitelisted chains that are not previously tracked are +// synced. The lowest sync height among all chain databases is returned. func loadChains() (syncHeight uint32, err error) { dbChains, err := db.OpenAll() if err != nil { diff --git a/engine/engine.go b/engine/engine.go index a60b713..df0ead9 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -49,7 +49,7 @@ const ( func Start(stop <-chan struct{}) (done <-chan struct{}) { log = _log.New("pkg", "engine") - // Verify Factom Blockchain NetworkID + // Verify Factom Blockchain NetworkID... if err := updateFactomHeight(); err != nil { log.Error(err) return @@ -74,7 +74,16 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { return } - if flag.StartScanHeight > -1 { // If -startscanheight was set... + if flag.IgnoreNewChains() { + // We can assume that all chains are synced to their + // chainheads, so we can start at the current height if we are + // ignoring new chains. + syncHeight = factomHeight + if len(Chains.trackedIDs) == 0 { + log.Error("no chains to track") + return + } + } else if flag.StartScanHeight > -1 { // If -startscanheight was set... if flag.StartScanHeight > int32(factomHeight) { log.Errorf("-startscanheight %v > Factom height (%v)", flag.StartScanHeight, factomHeight) @@ -90,30 +99,19 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { // overflows for 0 but it's OK as long as we don't rely on the // value until the first scan loop. syncHeight = uint32(flag.StartScanHeight - 1) - } else if flag.IgnoreNewChains() { - // We can assume that all chains are synced to their - // chainheads, so we can start at the current height if we are - // ignoring new chains. - syncHeight = factomHeight } else if syncHeight == 0 { // else if the syncHeight has not been set... switch flag.NetworkID { - case factom.Mainnet(): + case factom.MainnetID(): const mainnetStart = 163180 syncHeight = mainnetStart // Set for mainnet - case factom.Testnet(): + case factom.TestnetID(): const testnetStart = 60783 - setSyncHeight(testnetStart) // Set for testnet + syncHeight = testnetStart // Set for testnet default: var zero uint32 // Avoid constant overflow compile error. syncHeight = zero - 1 // Start scan at 0. } } - // Guard against syncing against a network with an earlier blockheight. - if syncHeight > factomHeight { - log.Errorf("Saved height (%v) > Factom height (%v)", - syncHeight, factomHeight) - return - } _done := make(chan struct{}) go engine(stop, _done) @@ -144,8 +142,8 @@ func engine(stop <-chan struct{}, done chan struct{}) { } }) - if !flag.IgnoreNewChains() { - log.Infof("Searching for FAT chains from block %v to %v...", + if !flag.IgnoreNewChains() && syncHeight < factomHeight { + log.Infof("Searching for new FAT chains from block %v to %v...", syncHeight+1, factomHeight) } var synced bool @@ -178,8 +176,10 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Check for process errors... select { case <-eblocks: - // We cannot consider this DBlock completed. - // Sync height will not be updated for all chains. + // One or more of the workers had an error and + // closed the eblocks channel. + // We cannot consider this DBlock completed, so + // we do not update sync height for all chains. return default: } diff --git a/factom/networkid.go b/factom/networkid.go index 23924e2..98df56c 100644 --- a/factom/networkid.go +++ b/factom/networkid.go @@ -6,12 +6,14 @@ import ( ) var ( - mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} - testnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA3} + mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} + testnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA3} + localnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA4} ) -func Mainnet() NetworkID { return mainnetID } -func Testnet() NetworkID { return testnetID } +func MainnetID() NetworkID { return mainnetID } +func TestnetID() NetworkID { return testnetID } +func LocalnetID() NetworkID { return localnetID } type NetworkID [4]byte @@ -21,6 +23,8 @@ func (n NetworkID) String() string { return "mainnet" case testnetID: return "testnet" + case localnetID: + return "localnet" default: return "custom: 0x" + Bytes(n[:]).String() } @@ -28,9 +32,11 @@ func (n NetworkID) String() string { func (n *NetworkID) Set(netIDStr string) error { switch strings.ToLower(netIDStr) { case "main", "mainnet": - *n = Mainnet() + *n = mainnetID case "test", "testnet": - *n = Testnet() + *n = testnetID + case "local", "localnet": + *n = localnetID default: if netIDStr[:2] == "0x" { // omit leading 0x @@ -56,6 +62,10 @@ func (n NetworkID) IsTestnet() bool { return n == testnetID } +func (n NetworkID) IsLocalnet() bool { + return n == localnetID +} + func (n NetworkID) IsCustom() bool { return !n.IsMainnet() && !n.IsTestnet() } diff --git a/flag/flag.go b/flag/flag.go index 288b8fb..3a25a4e 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -99,10 +99,6 @@ var ( "ecadr": "", "esadr": "", - "networkid": factom.Mainnet(), - - "whitelist": (Bytes32List)(nil), - "blacklist": (Bytes32List)(nil), "ignorenewchains": false, "skipdbvalidation": false, } @@ -171,7 +167,7 @@ var ( "-blacklist": complete.PredictAnything, "-ignorenewchains": complete.PredictNothing, - "-networkid": complete.PredictSet("main", "test"), + "-networkid": complete.PredictSet("main", "test", "local", "0x"), "-skipdbvalidation": complete.PredictNothing, } @@ -277,7 +273,7 @@ func Parse() { StartScanHeight = int32(startScanHeight) } if !flagset["networkid"] { - NetworkID = factom.Mainnet() + NetworkID = factom.MainnetID() } } @@ -317,6 +313,12 @@ func Validate() { } else { ECAdr = EsAdr.ECAddress() } + + if IgnoreNewChains() && flagset["startscanheight"] { + log.Fatal( + "-startscanheight incompatible with -ignorenewchains and -whitelist") + } + } func flagVar(v interface{}, name string) { diff --git a/srv/methods.go b/srv/methods.go index ae5ac06..ee08858 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -164,11 +164,14 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { defer chain.Put(conn) // Lookup Txs - nfTkns, _ := fat1.NewNFTokens(params.NFTokenID) + var nfTkns fat1.NFTokens + if params.NFTokenID != nil { + nfTkns, _ = fat1.NewNFTokens(params.NFTokenID) + } entries, err := db.SelectEntryByAddress(conn, params.StartHash, params.Addresses, nfTkns, params.ToFrom, params.Order, - int64(params.Page), int64(params.Limit)) + *params.Page, params.Limit) if err != nil { panic(err) } @@ -296,7 +299,7 @@ func getNFBalance(data json.RawMessage) interface{} { conn := chain.Pool.Get(nil) defer chain.Put(conn) tkns, err := db.SelectNFTokensByOwner(conn, params.Address, - int64(params.Page), int64(params.Limit), params.Order) + *params.Page, params.Limit, params.Order) if err != nil { panic(err) } @@ -429,7 +432,7 @@ func getNFTokens(data json.RawMessage) interface{} { defer chain.Put(conn) tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(conn, - params.Order, int64(params.Page), int64(params.Limit)) + params.Order, *params.Page, params.Limit) if err != nil { panic(err) } @@ -479,15 +482,20 @@ func getDaemonTokens(data json.RawMessage) interface{} { } type ResultGetDaemonProperties struct { - FatdVersion string `json:"fatdversion"` - APIVersion string `json:"apiversion"` + FatdVersion string `json:"fatdversion"` + APIVersion string `json:"apiversion"` + NetworkID factom.NetworkID `json:"factomnetworkid"` } func getDaemonProperties(data json.RawMessage) interface{} { if _, err := validate(data, nil); err != nil { return err } - return ResultGetDaemonProperties{FatdVersion: flag.Revision, APIVersion: APIVersion} + return ResultGetDaemonProperties{ + FatdVersion: flag.Revision, + APIVersion: APIVersion, + NetworkID: flag.NetworkID, + } } type ResultGetSyncStatus struct { diff --git a/srv/params.go b/srv/params.go index 88e1791..fcf35d3 100644 --- a/srv/params.go +++ b/srv/params.go @@ -78,12 +78,19 @@ func (p ParamsToken) ValidChainID() *factom.Bytes32 { } type ParamsPagination struct { - Page uint64 `json:"page,omitempty"` - Limit uint64 `json:"limit,omitempty"` - Order string `json:"order,omitempty"` + Page *uint64 `json:"page,omitempty"` + Limit uint64 `json:"limit,omitempty"` + Order string `json:"order,omitempty"` } func (p *ParamsPagination) IsValid() error { + if p.Page == nil { + p.Page = new(uint64) + *p.Page = 1 + } else if *p.Page == 0 { + return jrpc.InvalidParams( + `"order" value must be either "asc" or "desc"`) + } if p.Limit == 0 { p.Limit = 25 } From ccb1635de2d2f8a2d8a329baf8e00a3de19d87e6 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 10 Aug 2019 11:46:04 -0800 Subject: [PATCH 014/124] fix(srv,db): Return 0 if address not found --- db/address.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/db/address.go b/db/address.go index fee051e..d610483 100644 --- a/db/address.go +++ b/db/address.go @@ -54,6 +54,9 @@ func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, err stmt := conn.Prep(`SELECT "balance" FROM "addresses" WHERE "address" = ?;`) stmt.BindBytes(1, adr[:]) bal, err := sqlitex.ResultInt64(stmt) + if err != nil && err.Error() == "sqlite: statement has no results" { + return 0, nil + } if err != nil { return 0, err } From 1e0764b1ce2e028e4e49ba366c2ea2b7afcc7fad Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 10 Aug 2019 12:09:35 -0800 Subject: [PATCH 015/124] fix(factom): Set correct testnet NetworkID --- factom/networkid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/factom/networkid.go b/factom/networkid.go index 98df56c..cbdb830 100644 --- a/factom/networkid.go +++ b/factom/networkid.go @@ -7,7 +7,7 @@ import ( var ( mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} - testnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA3} + testnetID = [...]byte{0x88, 0x3e, 0x09, 0x3b} localnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA4} ) From 2b864deeb8b57de84fc556e77320da4819cec6db Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 10 Aug 2019 12:39:50 -0800 Subject: [PATCH 016/124] fix(db,srv): Bind tx hash bytes to SelectEntryByHash queries --- db/entry.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/db/entry.go b/db/entry.go index 95cf66f..120ccef 100644 --- a/db/entry.go +++ b/db/entry.go @@ -76,13 +76,14 @@ func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, error) { func SelectEntryByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `"hash" = ?;`) + stmt.BindBytes(1, hash[:]) defer stmt.Reset() return SelectEntry(stmt) } -func SelectEntryByHashValid(conn *sqlite.Conn, - hash *factom.Bytes32) (factom.Entry, error) { +func SelectEntryByHashValid(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `"hash" = ? AND "valid" = true;`) + stmt.BindBytes(1, hash[:]) defer stmt.Reset() return SelectEntry(stmt) } From 946629108c0c5366ec66f85d1f30561e0307df43 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 10 Aug 2019 12:40:58 -0800 Subject: [PATCH 017/124] feat(cli,srv): Return Issuance Entry Hash with get-stats --- cli/cmd/getchains.go | 3 ++- srv/methods.go | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cli/cmd/getchains.go b/cli/cmd/getchains.go index 1af17e2..5e75da4 100644 --- a/cli/cmd/getchains.go +++ b/cli/cmd/getchains.go @@ -127,6 +127,7 @@ Token ID: %q func printStats(chainID *factom.Bytes32, stats srv.ResultGetStats) { fmt.Printf(`Chain ID: %v Issuer Identity Chain ID: %v +Issuance Entry Hash: %v Token ID: %v Type: %v Symbol: %q @@ -136,7 +137,7 @@ Burned: %v Number of Transactions: %v Issuance Timestamp: %v `, - chainID, stats.IssuerChainID, stats.TokenID, + chainID, stats.IssuerChainID, stats.IssuanceHash, stats.TokenID, stats.Issuance.Type, stats.Issuance.Symbol, stats.Issuance.Supply, stats.CirculatingSupply, stats.Burned, stats.Transactions, diff --git a/srv/methods.go b/srv/methods.go index ee08858..c22b78c 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -316,6 +316,7 @@ func getNFBalance(data json.RawMessage) interface{} { type ResultGetStats struct { ParamsToken Issuance *fat.Issuance + IssuanceHash *factom.Bytes32 CirculatingSupply uint64 `json:"circulating"` Burned uint64 `json:"burned"` Transactions int64 `json:"transactions"` @@ -364,6 +365,7 @@ func getStats(data json.RawMessage) interface{} { res.ChainID = chain.ID res.TokenID = chain.TokenID res.IssuerChainID = chain.Identity.ChainID + res.IssuanceHash = chain.Issuance.Hash return res } From 3045b5a0ec304ad88a96746aeef53cd5a6ce073d Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 11 Aug 2019 14:31:32 -0800 Subject: [PATCH 018/124] fix(db): Check FAT-1 owner ID for each input tkn To simplify the assumptions required for the processing of txs, process inputs before outputs. Also add the very necessary check for the FAT-1 token ownership. BREAKING CHANGE: You must rebuild your database. --- db/apply.go | 105 +++++++++++++++++++++++++++++--------------------- db/nftoken.go | 14 +++++++ 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/db/apply.go b/db/apply.go index 2473a48..147232c 100644 --- a/db/apply.go +++ b/db/apply.go @@ -93,17 +93,6 @@ func (chain *Chain) applyFAT0Tx( return } - for adr, amount := range tx.Outputs { - var ai int64 - ai, err = chain.addressAdd(&adr, amount) - if err != nil { - return - } - if _, err = chain.insertAddressTransaction(ai, ei, true); err != nil { - return - } - } - if tx.IsCoinbase() { addIssued := tx.Inputs[fat.Coinbase()] if chain.Supply > 0 && @@ -114,20 +103,34 @@ func (chain *Chain) applyFAT0Tx( if _, err = chain.insertAddressTransaction(1, ei, false); err != nil { return } - err = chain.numIssuedAdd(addIssued) - return + if err = chain.numIssuedAdd(addIssued); err != nil { + return + } + } else { + for adr, amount := range tx.Inputs { + var ai int64 + ai, err = chain.addressSub(&adr, amount) + if err != nil { + return + } + if _, err = chain.insertAddressTransaction(ai, ei, + false); err != nil { + return + } + } } - for adr, amount := range tx.Inputs { + for adr, amount := range tx.Outputs { var ai int64 - ai, err = chain.addressSub(&adr, amount) + ai, err = chain.addressAdd(&adr, amount) if err != nil { return } - if _, err = chain.insertAddressTransaction(ai, ei, false); err != nil { + if _, err = chain.insertAddressTransaction(ai, ei, true); err != nil { return } } + return } @@ -204,28 +207,6 @@ func (chain *Chain) applyFAT1Tx( return } - for adr, nfTkns := range tx.Outputs { - var ai int64 - ai, err = chain.addressAdd(&adr, uint64(len(nfTkns))) - if err != nil { - return - } - var adrTxID int64 - adrTxID, err = chain.insertAddressTransaction(ai, ei, true) - if err != nil { - return - } - for nfID := range nfTkns { - if err = chain.setNFTokenOwner(nfID, ai, ei); err != nil { - return - } - if err = chain.insertNFTokenTransaction( - nfID, adrTxID); err != nil { - return - } - } - } - if tx.IsCoinbase() { nfTkns := tx.Inputs[fat.Coinbase()] addIssued := uint64(len(nfTkns)) @@ -252,27 +233,65 @@ func (chain *Chain) applyFAT1Tx( return } } - err = chain.numIssuedAdd(addIssued) - return + if err = chain.numIssuedAdd(addIssued); err != nil { + return + } + } else { + for adr, nfTkns := range tx.Inputs { + var ai int64 + ai, err = chain.addressSub(&adr, uint64(len(nfTkns))) + if err != nil { + return + } + var adrTxID int64 + adrTxID, err = chain.insertAddressTransaction(ai, ei, false) + if err != nil { + return + } + for nfTkn := range nfTkns { + var ownerID int64 + ownerID, err = SelectNFTokenOwnerID(chain.Conn, nfTkn) + if err != nil { + return + } + if ownerID == -1 { + err = fmt.Errorf("no such NFToken{%v}", nfTkn) + return + } + if ownerID != ai { + err = fmt.Errorf("NFToken{%v} not owned by %v", + nfTkn, adr) + return + } + if err = chain.insertNFTokenTransaction( + nfTkn, adrTxID); err != nil { + return + } + } + } } - for adr, nfTkns := range tx.Inputs { + for adr, nfTkns := range tx.Outputs { var ai int64 - ai, err = chain.addressSub(&adr, uint64(len(nfTkns))) + ai, err = chain.addressAdd(&adr, uint64(len(nfTkns))) if err != nil { return } var adrTxID int64 - adrTxID, err = chain.insertAddressTransaction(ai, ei, false) + adrTxID, err = chain.insertAddressTransaction(ai, ei, true) if err != nil { return } for nfID := range nfTkns { + if err = chain.setNFTokenOwner(nfID, ai, ei); err != nil { + return + } if err = chain.insertNFTokenTransaction( nfID, adrTxID); err != nil { return } } } + return } diff --git a/db/nftoken.go b/db/nftoken.go index fab50b8..d44adb2 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -5,6 +5,7 @@ import ( "fmt" "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) @@ -43,6 +44,19 @@ func (chain *Chain) insertNFTokenTransaction(nfID fat1.NFTokenID, adrTxID int64) return err } +func SelectNFTokenOwnerID(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (int64, error) { + stmt := conn.Prep(`SELECT "owner_id" FROM "nf_tokens" WHERE "id" = ?;`) + stmt.BindInt64(1, int64(nfTkn)) + ownerID, err := sqlitex.ResultInt64(stmt) + if err != nil && err.Error() == "sqlite: statement has no results" { + return -1, nil + } + if err != nil { + return -1, err + } + return ownerID, nil +} + func SelectNFToken(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (factom.FAAddress, factom.Bytes32, []byte, error) { var owner factom.FAAddress From 44ead7f92409aeb327a2dfa41b4f86e8d424b7a6 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 11 Aug 2019 14:37:55 -0800 Subject: [PATCH 019/124] fix(srv): Avoid panic from "send-transaction" Now we return and error describing the "send-transaction" method as "not implemented". --- srv/methods.go | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/srv/methods.go b/srv/methods.go index c22b78c..5056e93 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -455,15 +455,50 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { - return nil -} + return jrpc.NewError(-34000, "not implemented", "send-transaction") + if factom.Bytes32(flag.EsAdr).IsZero() { + return ErrorNoEC + } + params := ParamsSendTransaction{} + chain, err := validate(data, ¶ms) + if err != nil { + return err + } -func validFAT0Transaction(chain *engine.Chain, entry factom.Entry) error { - return nil -} + entry := params.Entry() + //txErr, dbErr := chain.TestApply(entry) + //if dbErr != nil { + // panic(err) + //} + //if txErr != nil { + // err := ErrorTransactionNotFound + // err.Data = txErr + // return err + //} -func validFAT1Transaction(chain *engine.Chain, entry factom.Entry) error { - return nil + balance, err := flag.ECAdr.GetBalance(c) + if err != nil { + panic(err) + } + cost, err := entry.Cost() + if err != nil { + rerr := ErrorInvalidTransaction + rerr.Data = err + return rerr + } + if balance < uint64(cost) { + return ErrorNoEC + } + txID, err := entry.ComposeCreate(c, flag.EsAdr) + if err != nil { + panic(err) + } + + return struct { + ChainID *factom.Bytes32 `json:"chainid"` + TxID *factom.Bytes32 `json:"txid"` + Hash *factom.Bytes32 `json:"entryhash"` + }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} } func getDaemonTokens(data json.RawMessage) interface{} { From f21f651d7604bf7f4e3c614783b174b04e60a591 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 11 Aug 2019 14:56:06 -0800 Subject: [PATCH 020/124] fix(db): Separate insert and update NTTokens --- db/apply.go | 7 +++++- db/gentestdb.go | 2 +- db/nftoken.go | 22 +++++++++++++----- ...bad3f56169f94966341018b1950542f3dd.sqlite3 | Bin 274432 -> 225280 bytes ...b10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 | Bin 114688 -> 114688 bytes 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/db/apply.go b/db/apply.go index 147232c..1d7b653 100644 --- a/db/apply.go +++ b/db/apply.go @@ -221,6 +221,11 @@ func (chain *Chain) applyFAT1Tx( return } for nfID := range nfTkns { + // Insert the NFToken with the coinbase address as a + // placeholder for the owner. + if err = chain.insertNFToken(nfID, 1, ei); err != nil { + return + } if err = chain.insertNFTokenTransaction( nfID, adrTxID); err != nil { return @@ -283,7 +288,7 @@ func (chain *Chain) applyFAT1Tx( return } for nfID := range nfTkns { - if err = chain.setNFTokenOwner(nfID, ai, ei); err != nil { + if err = chain.setNFTokenOwner(nfID, ai); err != nil { return } if err = chain.insertNFTokenTransaction( diff --git a/db/gentestdb.go b/db/gentestdb.go index 9aafed8..00934ec 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -68,7 +68,7 @@ func main() { } // We don't need the actual dbKeyMR - chain, err := db.OpenNew(dblock.KeyMR, first, Mainnet(), identity) + chain, err := db.OpenNew(dblock.KeyMR, first, MainnetID(), identity) if err != nil { log.Println(err) return diff --git a/db/nftoken.go b/db/nftoken.go index d44adb2..57a1933 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -10,18 +10,28 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, aID, eID int64) error { +func (chain *Chain) insertNFToken(nfID fat1.NFTokenID, adrID, entryID int64) error { stmt := chain.Conn.Prep(`INSERT INTO "nf_tokens" - ("id", "owner_id", "creation_entry_id") VALUES (?, ?, ?) - ON CONFLICT("id") DO - UPDATE SET "owner_id" = "excluded"."owner_id";`) + ("id", "owner_id", "creation_entry_id") VALUES (?, ?, ?);`) stmt.BindInt64(1, int64(nfID)) - stmt.BindInt64(2, aID) - stmt.BindInt64(3, eID) + stmt.BindInt64(2, adrID) + stmt.BindInt64(3, entryID) _, err := stmt.Step() return err } +func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, adrID int64) error { + stmt := chain.Conn.Prep(`UPDATE "nf_tokens" + SET "owner_id" = ? WHERE "id" = ?;`) + stmt.BindInt64(1, adrID) + stmt.BindInt64(2, int64(nfID)) + _, err := stmt.Step() + if chain.Conn.Changes() == 0 { + panic("no NFTokenID updated") + } + return err +} + func (chain *Chain) setNFTokenMetadata( nfID fat1.NFTokenID, metadata json.RawMessage) error { stmt := chain.Conn.Prep(`UPDATE "nf_tokens" diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 index 17fc81f3d41eb8df1e60716c4fbc0edc57e938ec..443ed6d0b681d22220b2454d52b93afa43caf2df 100644 GIT binary patch literal 225280 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

  • l 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

    XZ%rfQ0zQS)BqqY4l^nN==K>F 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 literal 274432 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

    ?hc>+I5Q!9ct8)xbLaJ4kb9>yTE}T3-ZR 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

    xKUG>>y0>(DX-?_J()7|5 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 Date: Sun, 11 Aug 2019 17:33:06 -0800 Subject: [PATCH 021/124] fix(db,cli): Add foreign key checks, add --starttx to get transactions --- cli/cmd/gettransactions.go | 4 ++-- db/chain.go | 3 +++ db/entry.go | 8 +++++++- db/sql.go | 1 - 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index 48f8a39..590f37b 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -131,7 +131,7 @@ func validateGetTxsFlags(cmd *cobra.Command, args []string) error { flags := cmd.LocalFlags() if len(transactionIDs) > 0 { for _, flgName := range []string{"page", "order", "page", "limit", - "starttxhash", "to", "from", "nftokenid", "address"} { + "starttx", "to", "from", "nftokenid", "address"} { if flags.Changed(flgName) { return fmt.Errorf("--%v is incompatible with TXID arguments", flgName) @@ -154,7 +154,7 @@ func validateGetTxsFlags(cmd *cobra.Command, args []string) error { } } - if !flags.Changed("starttxhash") { + if !flags.Changed("starttx") { paramsGetTxs.StartHash = nil } diff --git a/db/chain.go b/db/chain.go index 0d5a32a..eb65a46 100644 --- a/db/chain.go +++ b/db/chain.go @@ -192,6 +192,9 @@ func open(fname string) (*Chain, error) { if err := validateOrApplySchema(conn, chainDBSchema); err != nil { return nil, err } + if err := sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { + return nil, err + } flags = baseFlags | sqlite.SQLITE_OPEN_READONLY pool, err := sqlitex.Open(path, flags, PoolSize) if err != nil { diff --git a/db/entry.go b/db/entry.go index 120ccef..f6e87d3 100644 --- a/db/entry.go +++ b/db/entry.go @@ -35,7 +35,13 @@ func (chain *Chain) setEntryValid(id int64) error { stmt := chain.Conn.Prep(`UPDATE "entries" SET "valid" = 1 WHERE "id" = ?;`) stmt.BindInt64(1, id) _, err := stmt.Step() - return err + if err != nil { + return err + } + if chain.Conn.Changes() == 0 { + panic("no entries updated") + } + return nil } const SelectEntryWhere = `SELECT "hash", "data", "timestamp" FROM "entries" WHERE ` diff --git a/db/sql.go b/db/sql.go index 6eded52..3d2be96 100644 --- a/db/sql.go +++ b/db/sql.go @@ -26,7 +26,6 @@ type sql struct { // are called and the stmt is returned ready for its first Stmt.Step() call. func (sql *sql) Prep(conn *sqlite.Conn) *sqlite.Stmt { sql.sql.WriteString(`;`) - log.Debug(sql.sql.String()) stmt := conn.Prep(sql.sql.String()) param := 1 for _, bind := range sql.binds { From 5924c1e01e2d54a01d4cbbc7104ebfcb025ba947 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 13 Aug 2019 14:21:35 -0800 Subject: [PATCH 022/124] feat(srv,db): Implement "send-transaction", don't use ptr to db.Chain inside engine.Chain --- db/address.go | 15 ++--- db/apply.go | 135 ++++++++++++++++++++------------------------- db/chain.go | 85 ++++++++++++++++------------ db/chain_test.go | 1 + db/nftoken.go | 28 +++++----- engine/chain.go | 2 +- engine/chainmap.go | 2 +- srv/methods.go | 71 +++++++++++++++--------- 8 files changed, 179 insertions(+), 160 deletions(-) diff --git a/db/address.go b/db/address.go index d610483..f0115f1 100644 --- a/db/address.go +++ b/db/address.go @@ -22,17 +22,18 @@ func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) return SelectAddressID(chain.Conn, adr) } -func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error) { +func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, error) { if sub == 0 { // Allow tx's with zeros to result in an INSERT. - return chain.addressAdd(adr, 0) + id, err := chain.addressAdd(adr, 0) + return id, nil, err } id, err := SelectAddressID(chain.Conn, adr) if err != nil { - return id, err + return id, nil, err } if id < 0 { - return id, fmt.Errorf("insufficient balance: %v", adr) + return id, fmt.Errorf("insufficient balance: %v", adr), nil } stmt := chain.Conn.Prep( `UPDATE addresses SET balance = balance - ? WHERE rowid = ?;`) @@ -40,14 +41,14 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error) stmt.BindInt64(2, id) if _, err := stmt.Step(); err != nil { if sqlite.ErrCode(err) == sqlite.SQLITE_CONSTRAINT_CHECK { - return id, fmt.Errorf("insufficient balance: %v", adr) + return id, fmt.Errorf("insufficient balance: %v", adr), nil } - return id, err + return id, nil, err } if chain.Conn.Changes() == 0 { panic("no balances updated") } - return id, nil + return id, nil, nil } func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, error) { diff --git a/db/apply.go b/db/apply.go index 1d7b653..d3f17ce 100644 --- a/db/apply.go +++ b/db/apply.go @@ -59,35 +59,36 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) error { return nil } -func (chain *Chain) applyFAT0Tx( - ei int64, e factom.Entry) (tx fat0.Transaction, err error) { - tx = fat0.NewTransaction(e) - valid, err := chain.applyTx(ei, e, &tx) - if err != nil { - return +func applyTxRollback(chain *Chain, e factom.Entry, tx interface { + IsCoinbase() bool +}, + rollback func(*error), txErr, err *error) { + if *err != nil { + rollback(err) + } else if *txErr != nil { + rollback(txErr) + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, txErr) + } else { + var cbStr string + if tx.IsCoinbase() { + cbStr = "Coinbase " + } + chain.Log.Debugf("Valid %v %vTransaction: %v %+v", + chain.Type, cbStr, e.Hash, tx) } - if !valid { +} + +func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, + txErr, err error) { + tx = fat0.NewTransaction(e) + txErr, err = chain.applyTx(ei, e, &tx) + if err != nil || txErr != nil { return } - // Do not return, but log, any errors past this point as they are - // related to being unable to apply a transaction. - defer func() { - if err != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, err) - err = nil - } else { - var cbStr string - if tx.IsCoinbase() { - cbStr = "Coinbase " - } - chain.Log.Debugf("Valid %v %vTransaction: %v %+v", - chain.Type, cbStr, tx.Hash, tx) - } - }() - // But first rollback on any error. - defer sqlitex.Save(chain.Conn)(&err) + rollback := sqlitex.Save(chain.Conn) + defer applyTxRollback(chain, e, tx, rollback, &txErr, &err) if err = chain.setEntryValid(ei); err != nil { return @@ -95,22 +96,21 @@ func (chain *Chain) applyFAT0Tx( if tx.IsCoinbase() { addIssued := tx.Inputs[fat.Coinbase()] - if chain.Supply > 0 && - int64(chain.NumIssued+addIssued) > chain.Supply { - err = fmt.Errorf("coinbase exceeds max supply") + if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + txErr = fmt.Errorf("coinbase exceeds max supply") return } - if _, err = chain.insertAddressTransaction(1, ei, false); err != nil { + if err = chain.numIssuedAdd(addIssued); err != nil { return } - if err = chain.numIssuedAdd(addIssued); err != nil { + if _, err = chain.insertAddressTransaction(1, ei, false); err != nil { return } } else { for adr, amount := range tx.Inputs { var ai int64 - ai, err = chain.addressSub(&adr, amount) - if err != nil { + ai, txErr, err = chain.addressSub(&adr, amount) + if err != nil || txErr != nil { return } if _, err = chain.insertAddressTransaction(ai, ei, @@ -134,21 +134,22 @@ func (chain *Chain) applyFAT0Tx( return } -func (chain *Chain) applyTx(ei int64, e factom.Entry, tx fat.Validator) (bool, error) { +func (chain *Chain) applyTx(ei int64, e factom.Entry, tx fat.Validator) (error, error) { if err := tx.Validate(chain.ID1); err != nil { chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", e.Hash, chain.Type, err) - return false, nil + return err, nil } valid, err := checkEntryUniqueValid(chain.Conn, ei, e.Hash) if err != nil { - return false, err + return nil, err } if !valid { chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, "replay") + e.Hash, chain.Type, "replay: hash previously marked valid") + return fmt.Errorf("replay: hash previously marked valid"), nil } - return valid, nil + return nil, nil } func (chain *Chain) setApplyFunc() { @@ -160,12 +161,12 @@ func (chain *Chain) setApplyFunc() { switch chain.Type { case fat0.Type: chain.apply = func(ei int64, e factom.Entry) error { - _, err := chain.applyFAT0Tx(ei, e) + _, _, err := chain.ApplyFAT0Tx(ei, e) return err } case fat1.Type: chain.apply = func(ei int64, e factom.Entry) error { - _, err := chain.applyFAT1Tx(ei, e) + _, _, err := chain.ApplyFAT1Tx(ei, e) return err } default: @@ -173,35 +174,19 @@ func (chain *Chain) setApplyFunc() { } } -func (chain *Chain) applyFAT1Tx( - ei int64, e factom.Entry) (tx fat1.Transaction, err error) { +func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx fat1.Transaction, + txErr, err error) { tx = fat1.NewTransaction(e) - valid, err := chain.applyTx(ei, e, &tx) + txErr, err = chain.applyTx(ei, e, &tx) if err != nil { return } - if !valid { + if txErr != nil { return } - // Do not return, but log, any errors past this point as they are - // related to being unable to apply a transaction. - defer func() { - if err != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, err) - err = nil - } else { - var cbStr string - if tx.IsCoinbase() { - cbStr = "Coinbase " - } - chain.Log.Debugf("Valid %v %vTransaction: %v %+v", - chain.Type, cbStr, tx.Hash, tx) - } - }() - // But first rollback on any error. - defer sqlitex.Save(chain.Conn)(&err) + rollback := sqlitex.Save(chain.Conn) + defer applyTxRollback(chain, e, tx, rollback, &txErr, &err) if err = chain.setEntryValid(ei); err != nil { return @@ -210,9 +195,11 @@ func (chain *Chain) applyFAT1Tx( if tx.IsCoinbase() { nfTkns := tx.Inputs[fat.Coinbase()] addIssued := uint64(len(nfTkns)) - if chain.Supply > 0 && - int64(chain.NumIssued+addIssued) > chain.Supply { - err = fmt.Errorf("coinbase exceeds max supply") + if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + txErr = fmt.Errorf("coinbase exceeds max supply") + return + } + if err = chain.numIssuedAdd(addIssued); err != nil { return } var adrTxID int64 @@ -223,11 +210,11 @@ func (chain *Chain) applyFAT1Tx( for nfID := range nfTkns { // Insert the NFToken with the coinbase address as a // placeholder for the owner. - if err = chain.insertNFToken(nfID, 1, ei); err != nil { + txErr, err = chain.insertNFToken(nfID, 1, ei) + if err != nil || txErr != nil { return } - if err = chain.insertNFTokenTransaction( - nfID, adrTxID); err != nil { + if err = chain.insertNFTokenTransaction(nfID, adrTxID); err != nil { return } metadata := tx.TokenMetadata[nfID] @@ -238,14 +225,11 @@ func (chain *Chain) applyFAT1Tx( return } } - if err = chain.numIssuedAdd(addIssued); err != nil { - return - } } else { for adr, nfTkns := range tx.Inputs { var ai int64 - ai, err = chain.addressSub(&adr, uint64(len(nfTkns))) - if err != nil { + ai, txErr, err = chain.addressSub(&adr, uint64(len(nfTkns))) + if err != nil || txErr != nil { return } var adrTxID int64 @@ -260,11 +244,11 @@ func (chain *Chain) applyFAT1Tx( return } if ownerID == -1 { - err = fmt.Errorf("no such NFToken{%v}", nfTkn) + txErr = fmt.Errorf("no such NFToken{%v}", nfTkn) return } if ownerID != ai { - err = fmt.Errorf("NFToken{%v} not owned by %v", + txErr = fmt.Errorf("NFToken{%v} not owned by %v", nfTkn, adr) return } @@ -291,8 +275,7 @@ func (chain *Chain) applyFAT1Tx( if err = chain.setNFTokenOwner(nfID, ai); err != nil { return } - if err = chain.insertNFTokenTransaction( - nfID, adrTxID); err != nil { + if err = chain.insertNFTokenTransaction(nfID, adrTxID); err != nil { return } } diff --git a/db/chain.go b/db/chain.go index eb65a46..47d2144 100644 --- a/db/chain.go +++ b/db/chain.go @@ -50,27 +50,29 @@ type Chain struct { } func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, - identity factom.Identity) (chain *Chain, err error) { + identity factom.Identity) (chain Chain, err error) { fname := eb.ChainID.String() + dbFileExtension path := flag.DBPath + "/" + fname nameIDs := eb.Entries[0].ExtIDs if !fat.ValidTokenNameIDs(nameIDs) { - return nil, fmt.Errorf("invalid token chain Name IDs") + err = fmt.Errorf("invalid token chain Name IDs") + return } // Ensure that the database file doesn't already exist. _, err = os.Stat(path) if err == nil { - return nil, fmt.Errorf("already exists: %v", path) + err = fmt.Errorf("already exists: %v", path) + return } if !os.IsNotExist(err) { // Any other error is unexpected. - return nil, err + return } chain, err = open(fname) if err != nil { - return nil, err + return } defer func() { if err != nil { @@ -88,38 +90,36 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network chain.SyncDBKeyMR = dbKeyMR chain.NetworkID = networkID - if err := chain.insertMetadata(); err != nil { - return nil, err + if err = chain.insertMetadata(); err != nil { + return } // Ensure that the coinbase address has rowid = 1. coinbase := fat.Coinbase() - if _, err := chain.addressAdd(&coinbase, 0); err != nil { - return nil, err + if _, err = chain.addressAdd(&coinbase, 0); err != nil { + return } chain.setApplyFunc() - if err := chain.Apply(dbKeyMR, eb); err != nil { - return nil, err + if err = chain.Apply(dbKeyMR, eb); err != nil { + return } - return chain, nil + return } -func Open(fname string) (*Chain, error) { - chain, err := open(fname) +func Open(fname string) (chain Chain, err error) { + chain, err = open(fname) if err != nil { - return nil, err + return } - - if err := chain.loadMetadata(); err != nil { - return nil, err + if err = chain.loadMetadata(); err != nil { + return } - - return chain, nil + return } -func OpenAll() (chains []*Chain, err error) { +func OpenAll() (chains []Chain, err error) { log = _log.New("pkg", "db") // Try to create the database directory in case it doesn't already // exist. @@ -145,7 +145,7 @@ func OpenAll() (chains []*Chain, err error) { if err != nil { return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) } - chains = make([]*Chain, 0, len(files)) + chains = make([]Chain, 0, len(files)) for _, f := range files { fname := f.Name() chainID, err := fnameToChainID(fname) @@ -165,20 +165,20 @@ func OpenAll() (chains []*Chain, err error) { return chains, nil } func fnameToChainID(fname string) (*factom.Bytes32, error) { - invalidFNameErr := fmt.Errorf("invalid filename: %v", fname) + invalidFName := fmt.Errorf("invalid filename: %v", fname) if len(fname) != dbFileNameLen || fname[dbFileNameLen-len(dbFileExtension):dbFileNameLen] != dbFileExtension { - return nil, invalidFNameErr + return nil, invalidFName } chainID := factom.NewBytes32FromString(fname[0:64]) if chainID == nil { - return nil, invalidFNameErr + return nil, invalidFName } return chainID, nil } -func open(fname string) (*Chain, error) { +func open(fname string) (chain Chain, err error) { const baseFlags = sqlite.SQLITE_OPEN_WAL | sqlite.SQLITE_OPEN_URI | sqlite.SQLITE_OPEN_NOMUTEX @@ -186,24 +186,26 @@ func open(fname string) (*Chain, error) { flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE conn, err := sqlite.OpenConn(path, flags) if err != nil { - return nil, fmt.Errorf("sqlite.OpenConn(%q, %x): %v", + err = fmt.Errorf("sqlite.OpenConn(%q, %x): %v", path, flags, err) + return } - if err := validateOrApplySchema(conn, chainDBSchema); err != nil { - return nil, err + if err = validateOrApplySchema(conn, chainDBSchema); err != nil { + return } - if err := sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { - return nil, err + if err = sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { + return } - flags = baseFlags | sqlite.SQLITE_OPEN_READONLY + flags = baseFlags | sqlite.SQLITE_OPEN_READWRITE pool, err := sqlitex.Open(path, flags, PoolSize) if err != nil { - return nil, fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", + err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", path, flags, PoolSize, err) + return } - return &Chain{Conn: conn, Pool: pool, - Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension)), - }, nil + chain = Chain{Conn: conn, Pool: pool, + Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension))} + return } func (chain *Chain) Close() { @@ -215,3 +217,14 @@ func (chain *Chain) Close() { chain.Log.Errorf("chain.Conn.Close(): %v", err) } } + +var alwaysRollback = fmt.Errorf("always rollback") + +func (chain *Chain) Get() (*sqlite.Conn, func()) { + conn := chain.Pool.Get(nil) + rollback := sqlitex.Save(conn) + return conn, func() { + rollback(&alwaysRollback) + chain.Pool.Put(conn) + } +} diff --git a/db/chain_test.go b/db/chain_test.go index dd4afdf..7511c5e 100644 --- a/db/chain_test.go +++ b/db/chain_test.go @@ -17,6 +17,7 @@ func TestChainValidate(t *testing.T) { require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) for _, chain := range chains { + chain := chain defer chain.Close() assert.NoErrorf(t, chain.Validate(), "Chain{%v}.Validate()", chain.ID) } diff --git a/db/nftoken.go b/db/nftoken.go index 57a1933..6cac61c 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -10,19 +10,23 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -func (chain *Chain) insertNFToken(nfID fat1.NFTokenID, adrID, entryID int64) error { +func (chain *Chain) insertNFToken(nfID fat1.NFTokenID, adrID, entryID int64) (error, error) { stmt := chain.Conn.Prep(`INSERT INTO "nf_tokens" ("id", "owner_id", "creation_entry_id") VALUES (?, ?, ?);`) stmt.BindInt64(1, int64(nfID)) stmt.BindInt64(2, adrID) stmt.BindInt64(3, entryID) - _, err := stmt.Step() - return err + if _, err := stmt.Step(); err != nil { + if sqlite.ErrCode(err) == sqlite.SQLITE_CONSTRAINT_PRIMARYKEY { + return fmt.Errorf("NFTokenID{%v} already exists", nfID), nil + } + return nil, err + } + return nil, nil } func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, adrID int64) error { - stmt := chain.Conn.Prep(`UPDATE "nf_tokens" - SET "owner_id" = ? WHERE "id" = ?;`) + stmt := chain.Conn.Prep(`UPDATE "nf_tokens" SET "owner_id" = ? WHERE "id" = ?;`) stmt.BindInt64(1, adrID) stmt.BindInt64(2, int64(nfID)) _, err := stmt.Step() @@ -32,10 +36,8 @@ func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, adrID int64) error { return err } -func (chain *Chain) setNFTokenMetadata( - nfID fat1.NFTokenID, metadata json.RawMessage) error { - stmt := chain.Conn.Prep(`UPDATE "nf_tokens" - SET "metadata" = ? WHERE "id" = ?;`) +func (chain *Chain) setNFTokenMetadata(nfID fat1.NFTokenID, metadata json.RawMessage) error { + stmt := chain.Conn.Prep(`UPDATE "nf_tokens" SET "metadata" = ? WHERE "id" = ?;`) stmt.BindBytes(1, metadata) stmt.BindInt64(2, int64(nfID)) _, err := stmt.Step() @@ -50,6 +52,7 @@ func (chain *Chain) insertNFTokenTransaction(nfID fat1.NFTokenID, adrTxID int64) ("nf_tkn_id", "adr_tx_id") VALUES (?, ?);`) stmt.BindInt64(1, int64(nfID)) stmt.BindInt64(2, adrTxID) + _, err := stmt.Step() return err } @@ -67,8 +70,8 @@ func SelectNFTokenOwnerID(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (int64, error return ownerID, nil } -func SelectNFToken(conn *sqlite.Conn, - nfTkn fat1.NFTokenID) (factom.FAAddress, factom.Bytes32, []byte, error) { +func SelectNFToken(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (factom.FAAddress, + factom.Bytes32, []byte, error) { var owner factom.FAAddress var creationHash factom.Bytes32 stmt := conn.Prep(`SELECT "owner", "metadata", "creation_hash" @@ -95,8 +98,7 @@ func SelectNFToken(conn *sqlite.Conn, return owner, creationHash, metadata, nil } -func SelectNFTokens(conn *sqlite.Conn, - order string, page, limit uint64) ([]fat1.NFTokenID, +func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint64) ([]fat1.NFTokenID, []factom.FAAddress, []factom.Bytes32, [][]byte, error) { if page == 0 { return nil, nil, nil, nil, fmt.Errorf("invalid page") diff --git a/engine/chain.go b/engine/chain.go index 4ee1ce1..5dbb4ec 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -34,7 +34,7 @@ import ( type Chain struct { ChainStatus - *db.Chain + db.Chain } func (chain Chain) String() string { diff --git a/engine/chainmap.go b/engine/chainmap.go index 7544ec5..58aef94 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -186,7 +186,7 @@ func loadChains() (syncHeight uint32, err error) { // Open any whitelisted chains that do not already have databases. for id, chain := range Chains.m { - if chain.IsIgnored() || chain.Chain != nil { + if chain.IsIgnored() || chain.Conn != nil { continue } if err = chain.OpenNewByChainID(c, &id); err != nil { diff --git a/srv/methods.go b/srv/methods.go index 5056e93..945ebc4 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -106,8 +106,9 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() + entry, err := db.SelectEntryByHashValid(conn, params.Hash) if err != nil { panic(err) @@ -160,8 +161,8 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() // Lookup Txs var nfTkns fat1.NFTokens @@ -226,8 +227,9 @@ func getBalance(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() + balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) @@ -270,8 +272,8 @@ func getBalances(data json.RawMessage) interface{} { balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) @@ -296,8 +298,9 @@ func getNFBalance(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() + tkns, err := db.SelectNFTokensByOwner(conn, params.Address, *params.Page, params.Limit, params.Order) if err != nil { @@ -334,8 +337,9 @@ func getStats(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() + burned, err := db.SelectAddressBalance(conn, &coinbaseRCDHash) if err != nil { panic(err) @@ -390,8 +394,8 @@ func getNFToken(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() owner, creationHash, metadata, err := db.SelectNFToken(conn, *params.NFTokenID) if err != nil { @@ -430,8 +434,8 @@ func getNFTokens(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(conn, params.Order, *params.Page, params.Limit) @@ -455,7 +459,6 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { - return jrpc.NewError(-34000, "not implemented", "send-transaction") if factom.Bytes32(flag.EsAdr).IsZero() { return ErrorNoEC } @@ -465,16 +468,32 @@ func sendTransaction(data json.RawMessage) interface{} { return err } + conn, put := chain.Get() + defer put() + + count, err := db.SelectEntryCount(conn, false) + if err != nil { + panic(err) + } + + chain.Conn = conn + entry := params.Entry() - //txErr, dbErr := chain.TestApply(entry) - //if dbErr != nil { - // panic(err) - //} - //if txErr != nil { - // err := ErrorTransactionNotFound - // err.Data = txErr - // return err - //} + var txErr error + switch chain.Type { + case fat0.Type: + _, txErr, err = chain.ApplyFAT0Tx(count+1, entry) + case fat1.Type: + _, txErr, err = chain.ApplyFAT1Tx(count+1, entry) + } + if err != nil { + panic(err) + } + if txErr != nil { + err := ErrorInvalidTransaction + err.Data = txErr + return err + } balance, err := flag.ECAdr.GetBalance(c) if err != nil { From 34bfbd9591680bf725ccc73fb642bac3ba2896f4 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 13 Aug 2019 14:38:38 -0800 Subject: [PATCH 023/124] refactor(factom): Accept pointer to ChainID in PendingEntries{}.Entries --- factom/pendingentries.go | 6 +++--- factom/pendingentries_test.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/factom/pendingentries.go b/factom/pendingentries.go index 42a1c87..12dbc90 100644 --- a/factom/pendingentries.go +++ b/factom/pendingentries.go @@ -55,7 +55,7 @@ func (pe *PendingEntries) Get(c *Client) error { // Entries efficiently finds and returns all entries in pe for the given // chainID, if any exist. Otherwise, Entries returns nil. -func (pe PendingEntries) Entries(chainID Bytes32) []Entry { +func (pe PendingEntries) Entries(chainID *Bytes32) []Entry { // Find the first index of the entry with this chainID. ei := sort.Search(len(pe), func(i int) bool { var c []byte @@ -65,10 +65,10 @@ func (pe PendingEntries) Entries(chainID Bytes32) []Entry { } return bytes.Compare(c, chainID[:]) >= 0 }) - if ei < len(pe) && *pe[ei].ChainID == chainID { + if ei < len(pe) && *pe[ei].ChainID == *chainID { // Find all remaining entries with the chainID. for i, e := range pe[ei:] { - if *e.ChainID != chainID { + if *e.ChainID != *chainID { return pe[ei : ei+i] } } diff --git a/factom/pendingentries_test.go b/factom/pendingentries_test.go index 3923cf2..2c70369 100644 --- a/factom/pendingentries_test.go +++ b/factom/pendingentries_test.go @@ -66,20 +66,20 @@ func TestPendingEntries(t *testing.T) { return bytes.Compare(ci, cj) < 0 }), "not sorted") - es := pe.Entries(Bytes32{}) + es := pe.Entries(&Bytes32{}) if len(es) > 0 { assert.Nil(es[0].ChainID) } chainID := pe[len(pe)-1].ChainID if chainID != nil { - es := pe.Entries(*chainID) + es := pe.Entries(chainID) require.NotEmpty(es) for _, e := range es { assert.Equal(*e.ChainID, *chainID) } } - es = pe.Entries(*searchID) + es = pe.Entries(searchID) if len(es) == 0 { return } From 1dcd6118b340a0b2848ce25c36ad4709a737c3cf Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 15 Aug 2019 14:44:12 -0800 Subject: [PATCH 024/124] revert(db,engine): Use a ptr for db.Chain This reverts some changes made in 5924c1e which changed engine.Chain to use the db.Chain directly, instead of a pointer to it. That was problematic when using a closure around the chain pointer. --- db/chain.go | 79 +++++++++++++++++++--------------------------- db/chain_test.go | 1 - engine/chain.go | 2 +- engine/chainmap.go | 5 +-- srv/methods.go | 37 ++++++++++------------ 5 files changed, 54 insertions(+), 70 deletions(-) diff --git a/db/chain.go b/db/chain.go index 47d2144..2116514 100644 --- a/db/chain.go +++ b/db/chain.go @@ -50,29 +50,27 @@ type Chain struct { } func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, - identity factom.Identity) (chain Chain, err error) { + identity factom.Identity) (chain *Chain, err error) { fname := eb.ChainID.String() + dbFileExtension path := flag.DBPath + "/" + fname nameIDs := eb.Entries[0].ExtIDs if !fat.ValidTokenNameIDs(nameIDs) { - err = fmt.Errorf("invalid token chain Name IDs") - return + return nil, fmt.Errorf("invalid token chain Name IDs") } // Ensure that the database file doesn't already exist. _, err = os.Stat(path) if err == nil { - err = fmt.Errorf("already exists: %v", path) - return + return nil, fmt.Errorf("already exists: %v", path) } if !os.IsNotExist(err) { // Any other error is unexpected. - return + return nil, err } chain, err = open(fname) if err != nil { - return + return nil, err } defer func() { if err != nil { @@ -90,36 +88,38 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network chain.SyncDBKeyMR = dbKeyMR chain.NetworkID = networkID - if err = chain.insertMetadata(); err != nil { - return + if err := chain.insertMetadata(); err != nil { + return nil, err } // Ensure that the coinbase address has rowid = 1. coinbase := fat.Coinbase() - if _, err = chain.addressAdd(&coinbase, 0); err != nil { - return + if _, err := chain.addressAdd(&coinbase, 0); err != nil { + return nil, err } chain.setApplyFunc() - if err = chain.Apply(dbKeyMR, eb); err != nil { - return + if err := chain.Apply(dbKeyMR, eb); err != nil { + return nil, err } - return + return chain, nil } -func Open(fname string) (chain Chain, err error) { - chain, err = open(fname) +func Open(fname string) (*Chain, error) { + chain, err := open(fname) if err != nil { - return + return nil, err } - if err = chain.loadMetadata(); err != nil { - return + + if err := chain.loadMetadata(); err != nil { + return nil, err } - return + + return chain, nil } -func OpenAll() (chains []Chain, err error) { +func OpenAll() (chains []*Chain, err error) { log = _log.New("pkg", "db") // Try to create the database directory in case it doesn't already // exist. @@ -145,7 +145,7 @@ func OpenAll() (chains []Chain, err error) { if err != nil { return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) } - chains = make([]Chain, 0, len(files)) + chains = make([]*Chain, 0, len(files)) for _, f := range files { fname := f.Name() chainID, err := fnameToChainID(fname) @@ -178,7 +178,7 @@ func fnameToChainID(fname string) (*factom.Bytes32, error) { return chainID, nil } -func open(fname string) (chain Chain, err error) { +func open(fname string) (*Chain, error) { const baseFlags = sqlite.SQLITE_OPEN_WAL | sqlite.SQLITE_OPEN_URI | sqlite.SQLITE_OPEN_NOMUTEX @@ -186,26 +186,24 @@ func open(fname string) (chain Chain, err error) { flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE conn, err := sqlite.OpenConn(path, flags) if err != nil { - err = fmt.Errorf("sqlite.OpenConn(%q, %x): %v", + return nil, fmt.Errorf("sqlite.OpenConn(%q, %x): %v", path, flags, err) - return } - if err = validateOrApplySchema(conn, chainDBSchema); err != nil { - return + if err := validateOrApplySchema(conn, chainDBSchema); err != nil { + return nil, err } - if err = sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { - return + if err := sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { + return nil, err } - flags = baseFlags | sqlite.SQLITE_OPEN_READWRITE + flags = baseFlags | sqlite.SQLITE_OPEN_READONLY pool, err := sqlitex.Open(path, flags, PoolSize) if err != nil { - err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", + return nil, fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", path, flags, PoolSize, err) - return } - chain = Chain{Conn: conn, Pool: pool, - Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension))} - return + return &Chain{Conn: conn, Pool: pool, + Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension)), + }, nil } func (chain *Chain) Close() { @@ -217,14 +215,3 @@ func (chain *Chain) Close() { chain.Log.Errorf("chain.Conn.Close(): %v", err) } } - -var alwaysRollback = fmt.Errorf("always rollback") - -func (chain *Chain) Get() (*sqlite.Conn, func()) { - conn := chain.Pool.Get(nil) - rollback := sqlitex.Save(conn) - return conn, func() { - rollback(&alwaysRollback) - chain.Pool.Put(conn) - } -} diff --git a/db/chain_test.go b/db/chain_test.go index 7511c5e..dd4afdf 100644 --- a/db/chain_test.go +++ b/db/chain_test.go @@ -17,7 +17,6 @@ func TestChainValidate(t *testing.T) { require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) for _, chain := range chains { - chain := chain defer chain.Close() assert.NoErrorf(t, chain.Validate(), "Chain{%v}.Validate()", chain.ID) } diff --git a/engine/chain.go b/engine/chain.go index 5dbb4ec..4ee1ce1 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -34,7 +34,7 @@ import ( type Chain struct { ChainStatus - db.Chain + *db.Chain } func (chain Chain) String() string { diff --git a/engine/chainmap.go b/engine/chainmap.go index 58aef94..a2ceab4 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -184,9 +184,11 @@ func loadChains() (syncHeight uint32, err error) { Chains.m[*chain.ID] = chain } + dbChains = nil // Prevent closing any chains from this list. + // Open any whitelisted chains that do not already have databases. for id, chain := range Chains.m { - if chain.IsIgnored() || chain.Conn != nil { + if chain.IsIgnored() || chain.Chain != nil { continue } if err = chain.OpenNewByChainID(c, &id); err != nil { @@ -199,7 +201,6 @@ func loadChains() (syncHeight uint32, err error) { Chains.m[*chain.ID] = chain } - dbChains = nil // Prevent closing any chains from this list. return } func min(a, b uint32) uint32 { diff --git a/srv/methods.go b/srv/methods.go index 945ebc4..b076cac 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -106,9 +106,8 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { return err } - conn, put := chain.Get() - defer put() - + conn := chain.Pool.Get(nil) + defer chain.Put(conn) entry, err := db.SelectEntryByHashValid(conn, params.Hash) if err != nil { panic(err) @@ -161,8 +160,8 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { return err } - conn, put := chain.Get() - defer put() + conn := chain.Pool.Get(nil) + defer chain.Put(conn) // Lookup Txs var nfTkns fat1.NFTokens @@ -227,9 +226,8 @@ func getBalance(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() - + conn := chain.Pool.Get(nil) + defer chain.Put(conn) balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) @@ -272,8 +270,8 @@ func getBalances(data json.RawMessage) interface{} { balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) - conn, put := chain.Get() - defer put() + conn := chain.Pool.Get(nil) + defer chain.Put(conn) balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) @@ -298,9 +296,8 @@ func getNFBalance(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() - + conn := chain.Pool.Get(nil) + defer chain.Put(conn) tkns, err := db.SelectNFTokensByOwner(conn, params.Address, *params.Page, params.Limit, params.Order) if err != nil { @@ -337,9 +334,8 @@ func getStats(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() - + conn := chain.Pool.Get(nil) + defer chain.Put(conn) burned, err := db.SelectAddressBalance(conn, &coinbaseRCDHash) if err != nil { panic(err) @@ -394,8 +390,8 @@ func getNFToken(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() + conn := chain.Pool.Get(nil) + defer chain.Put(conn) owner, creationHash, metadata, err := db.SelectNFToken(conn, *params.NFTokenID) if err != nil { @@ -434,8 +430,8 @@ func getNFTokens(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() + conn := chain.Pool.Get(nil) + defer chain.Put(conn) tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(conn, params.Order, *params.Page, params.Limit) @@ -459,6 +455,7 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { + return jrpc.NewError(-34000, "not implemented", "send-transaction") if factom.Bytes32(flag.EsAdr).IsZero() { return ErrorNoEC } From 0cb94cbb1a8964af345d37748b4d471287251432 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 22 Aug 2019 16:56:07 -0800 Subject: [PATCH 025/124] feat(srv,engine,db): Implement 'send-transaction' method Finally you can use the 'send-transaction' RPC method. This also addresses a potential race condition between the srv and engine packages. We again go back to *not* using a pointer to the db.Chain. Rollbacks are easier to understand and also encompass the Chain struct itself and not just the database. --- db/apply.go | 194 ++++++++++++++++++++----------------- db/chain.go | 101 ++++++++++++------- db/chain_test.go | 1 + db/validate.go | 2 +- engine/chain.go | 2 +- engine/chainmap.go | 2 +- engine/chainstatus_test.go | 3 +- fat/entry.go | 8 +- fat/fat0/transaction.go | 2 + fat/fat1/transaction.go | 2 + go.mod | 6 +- go.sum | 191 ------------------------------------ srv/methods.go | 37 +++---- 13 files changed, 210 insertions(+), 341 deletions(-) diff --git a/db/apply.go b/db/apply.go index d3f17ce..3389940 100644 --- a/db/apply.go +++ b/db/apply.go @@ -10,17 +10,24 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -type applyFunc func(int64, factom.Entry) error +type applyFunc func(*Chain, int64, factom.Entry) error func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) { // Ensure entire EBlock is applied atomically. defer sqlitex.Save(chain.Conn)(&err) + defer func(chainCopy Chain) { + if err != nil { + // Reset chain on error + *chain = chainCopy + } + }(*chain) + + chain.Head = eb // Save latest EBlock. if err = chain.insertEBlock(eb, dbKeyMR); err != nil { return } - chain.Head = eb // Save and apply each entry. for _, e := range eb.Entries { @@ -29,47 +36,93 @@ func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) if err != nil { return } - if err = chain.apply(ei, e); err != nil { + if err = chain.apply(chain, ei, e); err != nil { return } } return } -func (chain *Chain) applyIssuance(ei int64, e factom.Entry) error { +var rollbackErr = fmt.Errorf("rollback") + +func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error) { + issuance := fat.NewIssuance(e) + rollback := sqlitex.Save(chain.Conn) + chainCopy := *chain + defer func() { + if err != nil || issueErr != nil { + rollback(&rollbackErr) + // Reset chain on error + *chain = chainCopy + } + if err != nil { + return + } + if issueErr != nil { + chain.Log.Debugf("Entry{%v}: invalid issuance: %v", + e.Hash, issueErr) + return + } + chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) + }() // The Identity must exist prior to issuance. - if !chain.Identity.IsPopulated() || - e.Timestamp.Before(chain.Identity.Timestamp) { - chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, - "created before identity") - return nil + if !chain.Identity.IsPopulated() || e.Timestamp.Before(chain.Identity.Timestamp) { + return } - issuance := fat.NewIssuance(e) - if err := issuance.Validate(chain.ID1); err != nil { - chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, err) - return nil + if issueErr = issuance.Validate(chain.ID1); issueErr != nil { + return } - // check sig and is valid - if err := chain.setInitEntryID(ei); err != nil { - return err + if err = chain.setInitEntryID(ei); err != nil { + return } chain.Issuance = issuance chain.setApplyFunc() - chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) - return nil + return } -func applyTxRollback(chain *Chain, e factom.Entry, tx interface { - IsCoinbase() bool -}, - rollback func(*error), txErr, err *error) { - if *err != nil { - rollback(err) - } else if *txErr != nil { - rollback(txErr) - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, txErr) - } else { +func (chain *Chain) setApplyFunc() { + if !chain.Issuance.IsPopulated() { + chain.apply = func(chain *Chain, ei int64, e factom.Entry) error { + _, err := chain.applyIssuance(ei, e) + return err + } + return + } + // Adapt to match ApplyFunc. + switch chain.Type { + case fat0.Type: + chain.apply = func(chain *Chain, ei int64, e factom.Entry) error { + _, _, err := chain.ApplyFAT0Tx(ei, e) + return err + } + case fat1.Type: + chain.apply = func(chain *Chain, ei int64, e factom.Entry) error { + _, _, err := chain.ApplyFAT1Tx(ei, e) + return err + } + default: + panic("invalid FAT type") + } +} + +func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { + rollback := sqlitex.Save(chain.Conn) + chainCopy := *chain + return func(txErr, err *error) { + e := tx.FactomEntry() + if *err != nil || *txErr != nil { + rollback(&rollbackErr) + // Reset chain on error + *chain = chainCopy + } + if *err != nil { + return + } + if *txErr != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, txErr) + return + } var cbStr string if tx.IsCoinbase() { cbStr = "Coinbase " @@ -79,18 +132,32 @@ func applyTxRollback(chain *Chain, e factom.Entry, tx interface { } } -func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, - txErr, err error) { - tx = fat0.NewTransaction(e) - txErr, err = chain.applyTx(ei, e, &tx) - if err != nil || txErr != nil { +func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { + if txErr = tx.Validate(chain.ID1); txErr != nil { + return txErr, nil + } + e := tx.FactomEntry() + valid, err := checkEntryUniqueValid(chain.Conn, ei, e.Hash) + if err != nil { + return nil, err + } + if !valid { + return fmt.Errorf("replay: hash previously marked valid"), nil + } + + if err = chain.setEntryValid(ei); err != nil { return } - rollback := sqlitex.Save(chain.Conn) - defer applyTxRollback(chain, e, tx, rollback, &txErr, &err) + return nil, nil +} - if err = chain.setEntryValid(ei); err != nil { +func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, + txErr, err error) { + tx = fat0.NewTransaction(e) + defer chain.Save(&tx)(&txErr, &err) + + if txErr, err = chain.applyTx(ei, &tx); err != nil || txErr != nil { return } @@ -134,61 +201,12 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, return } -func (chain *Chain) applyTx(ei int64, e factom.Entry, tx fat.Validator) (error, error) { - if err := tx.Validate(chain.ID1); err != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, err) - return err, nil - } - valid, err := checkEntryUniqueValid(chain.Conn, ei, e.Hash) - if err != nil { - return nil, err - } - if !valid { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, "replay: hash previously marked valid") - return fmt.Errorf("replay: hash previously marked valid"), nil - } - return nil, nil -} - -func (chain *Chain) setApplyFunc() { - if !chain.Issuance.IsPopulated() { - chain.apply = chain.applyIssuance - return - } - // Adapt to match ApplyFunc. - switch chain.Type { - case fat0.Type: - chain.apply = func(ei int64, e factom.Entry) error { - _, _, err := chain.ApplyFAT0Tx(ei, e) - return err - } - case fat1.Type: - chain.apply = func(ei int64, e factom.Entry) error { - _, _, err := chain.ApplyFAT1Tx(ei, e) - return err - } - default: - panic("invalid type") - } -} - func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx fat1.Transaction, txErr, err error) { tx = fat1.NewTransaction(e) - txErr, err = chain.applyTx(ei, e, &tx) - if err != nil { - return - } - if txErr != nil { - return - } + defer chain.Save(&tx)(&txErr, &err) - rollback := sqlitex.Save(chain.Conn) - defer applyTxRollback(chain, e, tx, rollback, &txErr, &err) - - if err = chain.setEntryValid(ei); err != nil { + if txErr, err = chain.applyTx(ei, &tx); err != nil || txErr != nil { return } diff --git a/db/chain.go b/db/chain.go index 2116514..f694acb 100644 --- a/db/chain.go +++ b/db/chain.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( @@ -50,27 +72,29 @@ type Chain struct { } func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, - identity factom.Identity) (chain *Chain, err error) { + identity factom.Identity) (chain Chain, err error) { fname := eb.ChainID.String() + dbFileExtension path := flag.DBPath + "/" + fname nameIDs := eb.Entries[0].ExtIDs if !fat.ValidTokenNameIDs(nameIDs) { - return nil, fmt.Errorf("invalid token chain Name IDs") + err = fmt.Errorf("invalid token chain Name IDs") + return } // Ensure that the database file doesn't already exist. _, err = os.Stat(path) if err == nil { - return nil, fmt.Errorf("already exists: %v", path) + err = fmt.Errorf("already exists: %v", path) + return } if !os.IsNotExist(err) { // Any other error is unexpected. - return nil, err + return } chain, err = open(fname) if err != nil { - return nil, err + return } defer func() { if err != nil { @@ -88,38 +112,36 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network chain.SyncDBKeyMR = dbKeyMR chain.NetworkID = networkID - if err := chain.insertMetadata(); err != nil { - return nil, err + if err = chain.insertMetadata(); err != nil { + return } // Ensure that the coinbase address has rowid = 1. coinbase := fat.Coinbase() - if _, err := chain.addressAdd(&coinbase, 0); err != nil { - return nil, err + if _, err = chain.addressAdd(&coinbase, 0); err != nil { + return } chain.setApplyFunc() - if err := chain.Apply(dbKeyMR, eb); err != nil { - return nil, err + if err = chain.Apply(dbKeyMR, eb); err != nil { + return } - return chain, nil + return } -func Open(fname string) (*Chain, error) { - chain, err := open(fname) +func Open(fname string) (chain Chain, err error) { + chain, err = open(fname) if err != nil { - return nil, err + return } - - if err := chain.loadMetadata(); err != nil { - return nil, err + if err = chain.loadMetadata(); err != nil { + return } - - return chain, nil + return } -func OpenAll() (chains []*Chain, err error) { +func OpenAll() (chains []Chain, err error) { log = _log.New("pkg", "db") // Try to create the database directory in case it doesn't already // exist. @@ -145,7 +167,7 @@ func OpenAll() (chains []*Chain, err error) { if err != nil { return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) } - chains = make([]*Chain, 0, len(files)) + chains = make([]Chain, 0, len(files)) for _, f := range files { fname := f.Name() chainID, err := fnameToChainID(fname) @@ -178,30 +200,32 @@ func fnameToChainID(fname string) (*factom.Bytes32, error) { return chainID, nil } -func open(fname string) (*Chain, error) { +func open(fname string) (chain Chain, err error) { const baseFlags = sqlite.SQLITE_OPEN_WAL | sqlite.SQLITE_OPEN_URI | - sqlite.SQLITE_OPEN_NOMUTEX + sqlite.SQLITE_OPEN_NOMUTEX | + sqlite.SQLITE_OPEN_READWRITE path := flag.DBPath + "/" + fname - flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE + flags := baseFlags | sqlite.SQLITE_OPEN_CREATE conn, err := sqlite.OpenConn(path, flags) if err != nil { - return nil, fmt.Errorf("sqlite.OpenConn(%q, %x): %v", - path, flags, err) + err = fmt.Errorf("sqlite.OpenConn(%q, %x): %v", path, flags, err) + return } - if err := validateOrApplySchema(conn, chainDBSchema); err != nil { - return nil, err + if err = validateOrApplySchema(conn, chainDBSchema); err != nil { + return } - if err := sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { - return nil, err + if err = sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { + return } - flags = baseFlags | sqlite.SQLITE_OPEN_READONLY + flags = baseFlags pool, err := sqlitex.Open(path, flags, PoolSize) if err != nil { - return nil, fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", + err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", path, flags, PoolSize, err) + return } - return &Chain{Conn: conn, Pool: pool, + return Chain{Conn: conn, Pool: pool, Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension)), }, nil } @@ -215,3 +239,12 @@ func (chain *Chain) Close() { chain.Log.Errorf("chain.Conn.Close(): %v", err) } } + +func (chain *Chain) Get() (*sqlite.Conn, func()) { + conn := chain.Pool.Get(nil) + rollback := sqlitex.Save(conn) + return conn, func() { + rollback(&rollbackErr) + chain.Pool.Put(conn) + } +} diff --git a/db/chain_test.go b/db/chain_test.go index dd4afdf..7511c5e 100644 --- a/db/chain_test.go +++ b/db/chain_test.go @@ -17,6 +17,7 @@ func TestChainValidate(t *testing.T) { require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) for _, chain := range chains { + chain := chain defer chain.Close() assert.NoErrorf(t, chain.Validate(), "Chain{%v}.Validate()", chain.ID) } diff --git a/db/validate.go b/db/validate.go index 0c064d4..ce8a164 100644 --- a/db/validate.go +++ b/db/validate.go @@ -60,7 +60,7 @@ func (chain Chain) Validate() (err error) { `) chain.NumIssued = 0 chain.Issuance = fat.Issuance{} - chain.apply = chain.applyIssuance + chain.setApplyFunc() eBlockStmt := read.Prep(SelectEBlockWhere + `true;`) // SELECT all EBlocks. entryStmt := read.Prep(SelectEntryWhere + `true;`) // SELECT all Entries. diff --git a/engine/chain.go b/engine/chain.go index 4ee1ce1..5dbb4ec 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -34,7 +34,7 @@ import ( type Chain struct { ChainStatus - *db.Chain + db.Chain } func (chain Chain) String() string { diff --git a/engine/chainmap.go b/engine/chainmap.go index a2ceab4..ccd2c29 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -188,7 +188,7 @@ func loadChains() (syncHeight uint32, err error) { // Open any whitelisted chains that do not already have databases. for id, chain := range Chains.m { - if chain.IsIgnored() || chain.Chain != nil { + if chain.IsIgnored() || chain.Chain.Conn != nil { continue } if err = chain.OpenNewByChainID(c, &id); err != nil { diff --git a/engine/chainstatus_test.go b/engine/chainstatus_test.go index 25d8770..baaec70 100644 --- a/engine/chainstatus_test.go +++ b/engine/chainstatus_test.go @@ -20,12 +20,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package engine_test +package engine import ( "testing" - . "github.com/Factom-Asset-Tokens/fatd/state" "github.com/stretchr/testify/assert" ) diff --git a/fat/entry.go b/fat/entry.go index 03a0848..a21fb0f 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -37,8 +37,10 @@ import ( "golang.org/x/crypto/ed25519" ) -type Validator interface { +type Transaction interface { Validate(factom.IDKey) error + IsCoinbase() bool + FactomEntry() factom.Entry } // Entry has variables and methods common to all fat0 entries. @@ -48,6 +50,10 @@ type Entry struct { factom.Entry `json:"-"` } +func (e Entry) FactomEntry() factom.Entry { + return e.Entry +} + // UnmarshalEntry unmarshals the content of the factom.Entry into the provided // variable v, disallowing all unknown fields. func (e Entry) UnmarshalEntry(v interface{}) error { diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index 5c3aa62..c2bbd8c 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -40,6 +40,8 @@ type Transaction struct { fat.Entry } +var _ fat.Transaction = &Transaction{} + // NewTransaction returns a Transaction initialized with the given entry. func NewTransaction(entry factom.Entry) Transaction { return Transaction{Entry: fat.Entry{Entry: entry}} diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index 625d6df..0737b2b 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -41,6 +41,8 @@ type Transaction struct { fat.Entry } +var _ fat.Transaction = &Transaction{} + // NewTransaction returns a Transaction initialized with the given entry. func NewTransaction(entry factom.Entry) Transaction { return Transaction{Entry: fat.Entry{Entry: entry}} diff --git a/go.mod b/go.mod index a893741..e7103a8 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,11 @@ module github.com/Factom-Asset-Tokens/fatd go 1.12 require ( - cloud.google.com/go v0.43.0 // indirect crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect - github.com/gocraft/dbr v0.0.0-20190131145710-48a049970bd2 - github.com/jinzhu/gorm v1.9.4 - github.com/jinzhu/now v1.0.0 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/magiconair/properties v1.8.1 // indirect github.com/mitchellh/go-homedir v1.1.0 @@ -28,6 +23,7 @@ require ( github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect + golang.org/x/text v0.3.2 // indirect ) replace github.com/gocraft/dbr => github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea diff --git a/go.sum b/go.sum index e4658a7..5ada8b6 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.2 h1:4y4L7BdHenTfZL0HervofNTHh9Ad6mNX72cQvl+5eH0= -cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea h1:8jg7gBfU9VSu5i12RTCgR4HzPt9ZZCLz+bz9/qFcRtw= -github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea/go.mod h1:TWT34di9Z4gHhQ/LjVuBGMXKsQgkGhdwNh/ZvFmJ6DU= github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBnL4rLgeNaspUYnmnvkXcmDA3QO3rHBGgU= github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 h1:McSW/pP7K0/Ucjig6AJwW7Khph/XOMYhSB8v3YxMBl4= @@ -24,116 +13,59 @@ github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 h1:2gBp1 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 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/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 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/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3 h1:3mNLx0iFqaq/Ssxqkjte26072KMu96uz1VBlbiZhQU4= -github.com/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3/go.mod h1:EcO5fNtMZHCMjAvj8LE6T+5bphSdR6LQ75n+m1TtsFI= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jinzhu/gorm v1.9.4 h1:3KDoUjMEfH58nweXdD5Dng222YiwOVUNFShENhehJyQ= -github.com/jinzhu/gorm v1.9.4/go.mod h1:7ZYqlk/T0SqZip7ZOIL1aC/sjDj+dJo6sN98WljHFXY= -github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k= -github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns= -github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= -github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -145,21 +77,12 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -167,40 +90,24 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/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/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -234,166 +141,68 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= -go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= -google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/srv/methods.go b/srv/methods.go index b076cac..4ab485c 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -106,8 +106,9 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() + entry, err := db.SelectEntryByHashValid(conn, params.Hash) if err != nil { panic(err) @@ -154,14 +155,15 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { if err != nil { return err } + if params.NFTokenID != nil && chain.Type != fat1.Type { err := ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() // Lookup Txs var nfTkns fat1.NFTokens @@ -226,8 +228,8 @@ func getBalance(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) @@ -270,8 +272,8 @@ func getBalances(data json.RawMessage) interface{} { balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() balance, err := db.SelectAddressBalance(conn, params.Address) if err != nil { panic(err) @@ -296,8 +298,8 @@ func getNFBalance(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() tkns, err := db.SelectNFTokensByOwner(conn, params.Address, *params.Page, params.Limit, params.Order) if err != nil { @@ -334,8 +336,9 @@ func getStats(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() + burned, err := db.SelectAddressBalance(conn, &coinbaseRCDHash) if err != nil { panic(err) @@ -390,8 +393,8 @@ func getNFToken(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() owner, creationHash, metadata, err := db.SelectNFToken(conn, *params.NFTokenID) if err != nil { @@ -430,8 +433,8 @@ func getNFTokens(data json.RawMessage) interface{} { return err } - conn := chain.Pool.Get(nil) - defer chain.Put(conn) + conn, put := chain.Get() + defer put() tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(conn, params.Order, *params.Page, params.Limit) @@ -455,7 +458,6 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { - return jrpc.NewError(-34000, "not implemented", "send-transaction") if factom.Bytes32(flag.EsAdr).IsZero() { return ErrorNoEC } @@ -473,6 +475,7 @@ func sendTransaction(data json.RawMessage) interface{} { panic(err) } + // Replace the conn chain.Conn = conn entry := params.Entry() From 2b182e60974ff62a1f6aa8d23ecb3b9e655fe8ab Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 10:22:40 -0800 Subject: [PATCH 026/124] fix(flag): Set default factomd/wallet timeouts --- flag/flag.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flag/flag.go b/flag/flag.go index 3a25a4e..a724088 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -83,14 +83,14 @@ var ( "apiaddress": ":8078", "s": "http://localhost:8088", - "factomdtimeout": time.Duration(0), + "factomdtimeout": 20 * time.Second, "factomduser": "", "factomdpassword": "", //"factomdcert": "", //"factomdtls": false, "w": "http://localhost:8089", - "wallettimeout": time.Duration(0), + "wallettimeout": 10 * time.Second, "walletuser": "", "walletpassword": "", //"walletcert": "", From ba9ae017c1d8447956c4a0bf60348d6ed0bc873e Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 10:47:47 -0800 Subject: [PATCH 027/124] feat(engine): Add a lockfile so that multiple fatd processes do not corrupt the db --- engine/engine.go | 42 +++++++++++++++++++++++++++++++++++++----- go.mod | 1 + go.sum | 2 ++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index df0ead9..29a197c 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -24,17 +24,21 @@ package engine import ( "fmt" + "path/filepath" "sync" "time" + "github.com/nightlyone/lockfile" + "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/flag" _log "github.com/Factom-Asset-Tokens/fatd/log" ) var ( - log _log.Log - c = flag.FactomClient + log _log.Log + c = flag.FactomClient + lockFile lockfile.Lockfile ) const ( @@ -49,6 +53,25 @@ const ( func Start(stop <-chan struct{}) (done <-chan struct{}) { log = _log.New("pkg", "engine") + // Try to create a lockfile + lockFilePath, err := filepath.Abs(flag.DBPath + "/db.lock") + lockFile, err = lockfile.New(lockFilePath) + if err != nil { + log.Error(err) + return + } + if err = lockFile.TryLock(); err != nil { + log.Errorf("Database in use by other process.") + return + } + defer func() { + if done == nil { + if err := lockFile.Unlock(); err != nil { + log.Errorf("lockFile.Unlock(): %v", err) + } + } + }() + // Verify Factom Blockchain NetworkID... if err := updateFactomHeight(); err != nil { log.Error(err) @@ -67,12 +90,16 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { } // Load and sync all existing and whitelisted chains. - var err error syncHeight, err = loadChains() if err != nil { log.Error(err) return } + defer func() { + if done == nil { + Chains.Close() + } + }() if flag.IgnoreNewChains() { // We can assume that all chains are synced to their @@ -121,8 +148,13 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { const numWorkers = 8 func engine(stop <-chan struct{}, done chan struct{}) { - defer close(done) - defer Chains.Close() + defer func() { + Chains.Close() + if err := lockFile.Unlock(); err != nil { + log.Errorf("lockFile.Unlock(): %v", err) + } + close(done) + }() // Launch workers eblocks := make(chan factom.EBlock) diff --git a/go.mod b/go.mod index e7103a8..002a10b 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/magiconair/properties v1.8.1 // indirect github.com/mitchellh/go-homedir v1.1.0 + github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 github.com/pelletier/go-toml v1.4.0 // indirect github.com/posener/complete v1.2.1 github.com/rs/cors v1.6.0 diff --git a/go.sum b/go.sum index 5ada8b6..a36a258 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 h1:+2OJrU8cmOstEoh0uQvYemRGVH1O6xtO2oANUWHFnP0= +github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= From 8ae99ab190dad588cc13b9910198475ff0f336ba Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 11:20:06 -0800 Subject: [PATCH 028/124] feat(factom): Populate Entry.Hash in UnmarshalBinary and ComputeHash --- factom/entry.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/factom/entry.go b/factom/entry.go index e575cad..38e5caf 100644 --- a/factom/entry.go +++ b/factom/entry.go @@ -426,6 +426,14 @@ func (e *Entry) UnmarshalBinary(data []byte) error { } e.Content = data[pos:] + + // Compute and save entry hash if not populated. + if e.Hash == nil { + e.Hash = new(Bytes32) + } + if e.Hash.IsZero() { + *e.Hash = EntryHash(data) + } return nil } @@ -433,7 +441,12 @@ func (e *Entry) UnmarshalBinary(data []byte) error { // representation of the Entry. func (e *Entry) ComputeHash() (Bytes32, error) { data, err := e.MarshalBinary() - return EntryHash(data), err + if err != nil { + return Bytes32{}, err + } + hash := EntryHash(data) + e.Hash = &hash + return hash, err } // EntryHash returns the Entry hash of data. Entry's are hashed via: From 831b786467f7ef3592995c9c1d32053cca41b2e9 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 11:52:57 -0800 Subject: [PATCH 029/124] fix(srv): Fix panic in send-transaction --- db/apply.go | 4 ++-- db/entry.go | 2 +- srv/methods.go | 62 +++++++++++++++++++++++++++----------------------- srv/params.go | 38 +++++++++++++++++++++++-------- 4 files changed, 65 insertions(+), 41 deletions(-) diff --git a/db/apply.go b/db/apply.go index 3389940..b683c35 100644 --- a/db/apply.go +++ b/db/apply.go @@ -32,7 +32,7 @@ func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) // Save and apply each entry. for _, e := range eb.Entries { var ei int64 - ei, err = chain.insertEntry(e, chain.Head.Sequence) + ei, err = chain.InsertEntry(e, chain.Head.Sequence) if err != nil { return } @@ -120,7 +120,7 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { } if *txErr != nil { chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, txErr) + e.Hash, chain.Type, *txErr) return } var cbStr string diff --git a/db/entry.go b/db/entry.go index f6e87d3..6366e74 100644 --- a/db/entry.go +++ b/db/entry.go @@ -11,7 +11,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -func (chain *Chain) insertEntry(e factom.Entry, ebSeq uint32) (int64, error) { +func (chain *Chain) InsertEntry(e factom.Entry, ebSeq uint32) (int64, error) { data, err := e.MarshalBinary() if err != nil { panic(fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err)) diff --git a/srv/methods.go b/srv/methods.go index 4ab485c..cca2934 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -458,65 +458,69 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { - if factom.Bytes32(flag.EsAdr).IsZero() { - return ErrorNoEC - } params := ParamsSendTransaction{} chain, err := validate(data, ¶ms) if err != nil { return err } + if !params.DoNotSend && factom.Bytes32(flag.EsAdr).IsZero() { + return ErrorNoEC + } conn, put := chain.Get() defer put() - count, err := db.SelectEntryCount(conn, false) - if err != nil { - panic(err) - } - // Replace the conn chain.Conn = conn entry := params.Entry() + var ei int64 + ei, err = chain.InsertEntry(entry, chain.Head.Sequence) + if err != nil { + panic(err) + } + var txErr error switch chain.Type { case fat0.Type: - _, txErr, err = chain.ApplyFAT0Tx(count+1, entry) + _, txErr, err = chain.ApplyFAT0Tx(ei, entry) case fat1.Type: - _, txErr, err = chain.ApplyFAT1Tx(count+1, entry) + _, txErr, err = chain.ApplyFAT1Tx(ei, entry) } if err != nil { panic(err) } if txErr != nil { err := ErrorInvalidTransaction - err.Data = txErr + err.Data = txErr.Error() return err } - balance, err := flag.ECAdr.GetBalance(c) - if err != nil { - panic(err) - } - cost, err := entry.Cost() - if err != nil { - rerr := ErrorInvalidTransaction - rerr.Data = err - return rerr - } - if balance < uint64(cost) { - return ErrorNoEC - } - txID, err := entry.ComposeCreate(c, flag.EsAdr) - if err != nil { - panic(err) + var txID *factom.Bytes32 + if !params.DoNotSend { + balance, err := flag.ECAdr.GetBalance(c) + if err != nil { + panic(err) + } + cost, err := entry.Cost() + if err != nil { + rerr := ErrorInvalidTransaction + rerr.Data = err.Error() + return rerr + } + if balance < uint64(cost) { + return ErrorNoEC + } + txID, err = entry.ComposeCreate(c, flag.EsAdr) + if err != nil { + panic(err) + } } return struct { ChainID *factom.Bytes32 `json:"chainid"` - TxID *factom.Bytes32 `json:"txid"` - Hash *factom.Bytes32 `json:"entryhash"` + TxID *factom.Bytes32 `json:"factomtx,omitempty"` + Hash *factom.Bytes32 `json:"fattx"` }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} } diff --git a/srv/params.go b/srv/params.go index fcf35d3..094e80d 100644 --- a/srv/params.go +++ b/srv/params.go @@ -238,25 +238,45 @@ func (p *ParamsGetAllNFTokens) IsValid() error { type ParamsSendTransaction struct { ParamsToken - ExtIDs []factom.Bytes `json:"extids"` - Content factom.Bytes `json:"content"` + ExtIDs []factom.Bytes `json:"extids,omitempty"` + Content factom.Bytes `json:"content,omitempty"` + Raw factom.Bytes `json:"raw,omitempty"` + DoNotSend bool `json:"donotsend,omitempty"` + entry factom.Entry } -func (p ParamsSendTransaction) IsValid() error { +func (p *ParamsSendTransaction) IsValid() error { + if p.Raw != nil { + if p.ExtIDs != nil || p.Content != nil { + return jrpc.InvalidParams( + `"raw cannot be used with "content" or "extids"`) + } + if err := p.entry.UnmarshalBinary(p.Raw); err != nil { + return jrpc.InvalidParams(err) + } + p.entry.Timestamp = time.Now() + p.ChainID = p.entry.ChainID + return nil + } if err := p.ParamsToken.IsValid(); err != nil { return err } if len(p.Content) == 0 || len(p.ExtIDs) == 0 { - return jrpc.InvalidParams(`required: "content" and "extids"`) + return jrpc.InvalidParams(`required: "raw" or "content" and "extids"`) } - return nil -} - -func (p ParamsSendTransaction) Entry() factom.Entry { - return factom.Entry{ + p.entry = factom.Entry{ ExtIDs: p.ExtIDs, Content: p.Content, Timestamp: time.Now(), ChainID: p.ChainID, } + if _, err := p.entry.ComputeHash(); err != nil { + return jrpc.InvalidParams(err) + } + + return nil +} + +func (p ParamsSendTransaction) Entry() factom.Entry { + return p.entry } From 4b0610e9c6331e015f1753a4cc9d1950b583a251 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 11:59:32 -0800 Subject: [PATCH 030/124] fix(srv): Improve validation of ParamsSendTransactions and naming of result --- srv/methods.go | 4 ++-- srv/params.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/srv/methods.go b/srv/methods.go index cca2934..3af7da7 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -519,8 +519,8 @@ func sendTransaction(data json.RawMessage) interface{} { return struct { ChainID *factom.Bytes32 `json:"chainid"` - TxID *factom.Bytes32 `json:"factomtx,omitempty"` - Hash *factom.Bytes32 `json:"fattx"` + TxID *factom.Bytes32 `json:"factomtxid,omitempty"` + Hash *factom.Bytes32 `json:"fattxid"` }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} } diff --git a/srv/params.go b/srv/params.go index 094e80d..8e1d630 100644 --- a/srv/params.go +++ b/srv/params.go @@ -247,7 +247,7 @@ type ParamsSendTransaction struct { func (p *ParamsSendTransaction) IsValid() error { if p.Raw != nil { - if p.ExtIDs != nil || p.Content != nil { + if p.ExtIDs != nil || p.Content != nil || p.ParamsToken != (ParamsToken{}) { return jrpc.InvalidParams( `"raw cannot be used with "content" or "extids"`) } From 8f806cfffab9355320a7185a4136f8ac26bc4c25 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 12:09:19 -0800 Subject: [PATCH 031/124] fix(srv): Do not call entryhash a tx id --- cli/cmd/gettransactions.go | 2 +- srv/methods.go | 8 ++++---- srv/params.go | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index 590f37b..0ed0baf 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -51,7 +51,7 @@ var getTxsCmd = func() *cobra.Command { cmd := &cobra.Command{ DisableFlagsInUseLine: true, Use: ` -transactions --chainid TXID... +transactions --chainid TXHASH... fat-cli get transactions --chainid [--starttx ] [--page ] [--limit ] [--order <"asc" | "desc">] diff --git a/srv/methods.go b/srv/methods.go index 3af7da7..068233a 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -463,7 +463,7 @@ func sendTransaction(data json.RawMessage) interface{} { if err != nil { return err } - if !params.DoNotSend && factom.Bytes32(flag.EsAdr).IsZero() { + if !params.DryRun && factom.Bytes32(flag.EsAdr).IsZero() { return ErrorNoEC } @@ -497,7 +497,7 @@ func sendTransaction(data json.RawMessage) interface{} { } var txID *factom.Bytes32 - if !params.DoNotSend { + if !params.DryRun { balance, err := flag.ECAdr.GetBalance(c) if err != nil { panic(err) @@ -519,8 +519,8 @@ func sendTransaction(data json.RawMessage) interface{} { return struct { ChainID *factom.Bytes32 `json:"chainid"` - TxID *factom.Bytes32 `json:"factomtxid,omitempty"` - Hash *factom.Bytes32 `json:"fattxid"` + TxID *factom.Bytes32 `json:"txid,omitempty"` + Hash *factom.Bytes32 `json:"entryhash"` }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} } diff --git a/srv/params.go b/srv/params.go index 8e1d630..1ef8163 100644 --- a/srv/params.go +++ b/srv/params.go @@ -238,11 +238,11 @@ func (p *ParamsGetAllNFTokens) IsValid() error { type ParamsSendTransaction struct { ParamsToken - ExtIDs []factom.Bytes `json:"extids,omitempty"` - Content factom.Bytes `json:"content,omitempty"` - Raw factom.Bytes `json:"raw,omitempty"` - DoNotSend bool `json:"donotsend,omitempty"` - entry factom.Entry + ExtIDs []factom.Bytes `json:"extids,omitempty"` + Content factom.Bytes `json:"content,omitempty"` + Raw factom.Bytes `json:"raw,omitempty"` + DryRun bool `json:"dryrun,omitempty"` + entry factom.Entry } func (p *ParamsSendTransaction) IsValid() error { From e5f029b1cf1a2371e003f091105011eccda34bcb Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 28 Aug 2019 14:53:31 -0800 Subject: [PATCH 032/124] feat(srv,engine): Add startup INFO statements --- engine/engine.go | 4 ++++ srv/srv.go | 1 + 2 files changed, 5 insertions(+) diff --git a/engine/engine.go b/engine/engine.go index 29a197c..38280f0 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -90,6 +90,10 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { } // Load and sync all existing and whitelisted chains. + log.Infof("Loading chain databases from %v...", flag.DBPath) + if flag.SkipDBValidation { + log.Warn("Skipping database validation...") + } syncHeight, err = loadChains() if err != nil { log.Error(err) diff --git a/srv/srv.go b/srv/srv.go index f063bec..c12e776 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -67,6 +67,7 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { // Start server. _done := make(chan struct{}) + log.Infof("Listening on %v...", flag.APIAddress) go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Errorf("srv.ListenAndServe(): %v", err) From cf53f8bf986b0884cadf4d06433eba4bb729586b Mon Sep 17 00:00:00 2001 From: esalinger Date: Fri, 30 Aug 2019 14:02:27 -0700 Subject: [PATCH 033/124] Added Username, Password, and TLS flags. We should now support basic auth and TLS --- flag/flag.go | 41 +++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ srv/srv.go | 27 ++++++++++++++++++++++++--- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/flag/flag.go b/flag/flag.go index a724088..bd74a90 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -96,6 +96,12 @@ var ( //"walletcert": "", //"wallettls": false, + "username": "", + "password": "", + "cert": "", + "certkey": "", + "tls": false, + "ecadr": "", "esadr": "", @@ -126,6 +132,11 @@ var ( //"walletcert": "The TLS certificate that will be provided by the factom-walletd API server", //"wallettls": "Set to true to use TLS when accessing the factom-walletd API", + "username": "Username for connections to FATD server.", + "password": "Password for connections to FATD server.", + "cert": "Path to TLS certificate for the FATD server.", + "tls": "Set to true to use TLS when accessing the FATD server.", + "ecadr": "Entry Credit Public Address to use to pay for Factom entries", "esadr": "Entry Credit Secret Address to use to pay for Factom entries", @@ -157,6 +168,12 @@ var ( //"-walletcert": complete.PredictFiles("*"), //"-wallettls": complete.PredictNothing, + "-username": complete.PredictNothing, + "-password": complete.PredictNothing, + "-cert": complete.PredictFiles("*"), + "-certkey": complete.PredictFiles("*"), + "-tls": complete.PredictNothing, + "-y": complete.PredictNothing, "-installcompletion": complete.PredictNothing, "-uninstallcompletion": complete.PredictNothing, @@ -194,6 +211,12 @@ var ( Whitelist, Blacklist Bytes32List ignoreNewChains bool SkipDBValidation bool + + Username string + Password string + TLSCertFile string + TLSKeyFile string + TLSEnable bool ) func init() { @@ -228,6 +251,13 @@ func init() { flagVar(&ignoreNewChains, "ignorenewchains") flagVar(&SkipDBValidation, "skipdbvalidation") + // Added in FatD authentication info. + flagVar(&Username, "username") + flagVar(&Password, "password") + flagVar(&TLSCertFile, "cert") + flagVar(&TLSKeyFile, "certkey") + flagVar(&TLSEnable, "tls") + // Add flags for self installing the CLI completion tool Completion = complete.New(os.Args[0], complete.Command{Flags: flags}) Completion.CLI.InstallName = "installcompletion" @@ -287,6 +317,10 @@ func Validate() { if len(FactomClient.Walletd.Password) > 0 { walletdPassword = "" } + authPassword := "\"\"" + if len(Password) > 0 { + authPassword = "" + } log.Debugf("-dbpath %#v", DBPath) log.Debugf("-apiaddress %#v", APIAddress) @@ -305,6 +339,9 @@ func Validate() { log.Debugf("-wallettimeout %v ", FactomClient.Walletd.Timeout) log.Debugf("-walletuser %#v", FactomClient.Walletd.User) log.Debugf("-walletpass %v ", walletdPassword) + + log.Debugf("-username %#v", Username) + log.Debugf("-password %v ", authPassword) //log.Debugf("-walletcert %#v", FactomClient.Walletd.TLSCertFile) debugPrintln() @@ -415,6 +452,10 @@ func HasWhitelist() bool { return len(Whitelist) > 0 } +func HasAuth() bool { + return Username != "" && Password != "" +} + func IgnoreNewChains() bool { return ignoreNewChains || HasWhitelist() } diff --git a/go.mod b/go.mod index 002a10b..7d21660 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 + github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/magiconair/properties v1.8.1 // indirect github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index a36a258..d4c3a3a 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/srv/srv.go b/srv/srv.go index c12e776..c8cbc6a 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -28,6 +28,7 @@ import ( jrpc "github.com/AdamSLevy/jsonrpc2/v11" "github.com/Factom-Asset-Tokens/fatd/flag" _log "github.com/Factom-Asset-Tokens/fatd/log" + "github.com/goji/httpauth" "github.com/rs/cors" ) @@ -59,17 +60,20 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { // Set up server. srvMux := http.NewServeMux() - srvMux.Handle("/", handler) - srvMux.Handle("/v1", handler) + + srvMux.Handle("/", handleAuth(handler)) + srvMux.Handle("/v1", handleAuth(handler)) + cors := cors.New(cors.Options{AllowedOrigins: []string{"*"}}) srv = http.Server{Handler: cors.Handler(srvMux)} + srv.Addr = flag.APIAddress // Start server. _done := make(chan struct{}) log.Infof("Listening on %v...", flag.APIAddress) go func() { - if err := srv.ListenAndServe(); err != http.ErrServerClosed { + if err := listenAndServe(srv); err != http.ErrServerClosed { log.Errorf("srv.ListenAndServe(): %v", err) } close(_done) @@ -86,3 +90,20 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { }() return _done } + +func listenAndServe(server http.Server) interface{} { + if flag.TLSEnable { + return server.ListenAndServeTLS(flag.TLSCertFile, flag.TLSKeyFile) + } else { + return server.ListenAndServe() + } + +} + +func handleAuth(handlerFunc http.HandlerFunc) http.Handler { + if flag.HasAuth() { + return (httpauth.SimpleBasicAuth(flag.Username, flag.Password))(handlerFunc) + } else { + return handlerFunc + } +} From 87896ed18945fa2d9adb1d4309ca11e2e680c3cf Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 30 Aug 2019 16:34:48 -0800 Subject: [PATCH 034/124] fix(srv): Reduce TLS and Auth logic, allow srv to exit on SIGINT --- flag/flag.go | 88 ++++++++++++++++++++++++++++++---------------------- srv/srv.go | 44 ++++++++++++-------------- 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/flag/flag.go b/flag/flag.go index bd74a90..01ecf28 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -47,7 +47,11 @@ var ( "dbpath": "DB_PATH", - "apiaddress": "API_ADDRESS", + "apiaddress": "API_ADDRESS", + "apiusername": "API_USERNAME", + "apipassword": "API_PASSWORD", + "apitlscert": "API_TLS_CERT", + "apitlskey": "API_TLS_KEY", "s": "FACTOMD_SERVER", "factomdtimeout": "FACTOMD_TIMEOUT", @@ -80,7 +84,11 @@ var ( "dbpath": "./fatd.db", - "apiaddress": ":8078", + "apiaddress": ":8078", + "apiusername": "", + "apipassword": "", + "apitlscert": "", + "apitlskey": "", "s": "http://localhost:8088", "factomdtimeout": 20 * time.Second, @@ -96,12 +104,6 @@ var ( //"walletcert": "", //"wallettls": false, - "username": "", - "password": "", - "cert": "", - "certkey": "", - "tls": false, - "ecadr": "", "esadr": "", @@ -115,7 +117,11 @@ var ( "dbpath": "Path to the folder containing all database files", - "apiaddress": "IPAddr:port# to bind to for serving the JSON RPC 2.0 API", + "apiaddress": "IPAddr:port# to bind to for serving the fatd API", + "apiusername": "Username required for connections to fatd API", + "apipassword": "Password required for connections to fatd API", + "apitlscert": "Path to TLS certificate for the fatd API", + "apitlskey": "Path to TLS Key for the fatd API", "s": "IPAddr:port# of factomd API to use to access blockchain", "factomdtimeout": "Timeout for factomd API requests, 0 means never timeout", @@ -132,11 +138,6 @@ var ( //"walletcert": "The TLS certificate that will be provided by the factom-walletd API server", //"wallettls": "Set to true to use TLS when accessing the factom-walletd API", - "username": "Username for connections to FATD server.", - "password": "Password for connections to FATD server.", - "cert": "Path to TLS certificate for the FATD server.", - "tls": "Set to true to use TLS when accessing the FATD server.", - "ecadr": "Entry Credit Public Address to use to pay for Factom entries", "esadr": "Entry Credit Secret Address to use to pay for Factom entries", @@ -152,7 +153,11 @@ var ( "-dbpath": complete.PredictFiles("*"), - "-apiaddress": complete.PredictAnything, + "-apiaddress": complete.PredictAnything, + "-apiusername": complete.PredictAnything, + "-apipassword": complete.PredictAnything, + "-apitlscert": complete.PredictFiles("*.cert"), + "-apitlskey": complete.PredictFiles("*.key"), "-s": complete.PredictAnything, "-factomdtimeout": complete.PredictAnything, @@ -212,11 +217,13 @@ var ( ignoreNewChains bool SkipDBValidation bool - Username string - Password string + HasAuth bool + Username string + Password string + + HasTLS bool TLSCertFile string TLSKeyFile string - TLSEnable bool ) func init() { @@ -227,6 +234,11 @@ func init() { flagVar(&DBPath, "dbpath") flagVar(&APIAddress, "apiaddress") + // Added in FatD authentication info. + flagVar(&Username, "apiusername") + flagVar(&Password, "apipassword") + flagVar(&TLSCertFile, "apitlscert") + flagVar(&TLSKeyFile, "apitlskey") flagVar(&ECAdr, "ecadr") flagVar(&EsAdr, "esadr") @@ -251,13 +263,6 @@ func init() { flagVar(&ignoreNewChains, "ignorenewchains") flagVar(&SkipDBValidation, "skipdbvalidation") - // Added in FatD authentication info. - flagVar(&Username, "username") - flagVar(&Password, "password") - flagVar(&TLSCertFile, "cert") - flagVar(&TLSKeyFile, "certkey") - flagVar(&TLSEnable, "tls") - // Add flags for self installing the CLI completion tool Completion = complete.New(os.Args[0], complete.Command{Flags: flags}) Completion.CLI.InstallName = "installcompletion" @@ -309,17 +314,17 @@ func Parse() { func Validate() { // Redact private data from debug output. - factomdPassword := "\"\"" + factomdPassword := `""` if len(FactomClient.Factomd.Password) > 0 { factomdPassword = "" } - walletdPassword := "\"\"" + walletdPassword := `""` if len(FactomClient.Walletd.Password) > 0 { walletdPassword = "" } - authPassword := "\"\"" + apiPassword := `""` if len(Password) > 0 { - authPassword = "" + apiPassword = "" } log.Debugf("-dbpath %#v", DBPath) @@ -332,17 +337,18 @@ func Validate() { log.Debugf("-factomdtimeout %v ", FactomClient.Factomd.Timeout) log.Debugf("-factomduser %#v", FactomClient.Factomd.User) log.Debugf("-factomdpass %v ", factomdPassword) - //log.Debugf("-factomdcert %#v", FactomClient.Factomd.TLSCertFile) debugPrintln() log.Debugf("-w %#v", FactomClient.WalletdServer) log.Debugf("-wallettimeout %v ", FactomClient.Walletd.Timeout) log.Debugf("-walletuser %#v", FactomClient.Walletd.User) log.Debugf("-walletpass %v ", walletdPassword) + debugPrintln() - log.Debugf("-username %#v", Username) - log.Debugf("-password %v ", authPassword) - //log.Debugf("-walletcert %#v", FactomClient.Walletd.TLSCertFile) + log.Debugf("-apiusername %#v", Username) + log.Debugf("-apipassword %v ", apiPassword) + log.Debugf("-apitlscert %#v", TLSCertFile) + log.Debugf("-apitlskey %#v", TLSKeyFile) debugPrintln() if factom.Bytes32(EsAdr).IsZero() { @@ -356,6 +362,18 @@ func Validate() { "-startscanheight incompatible with -ignorenewchains and -whitelist") } + if len(Username) > 0 || len(Password) > 0 { + if len(Username) == 0 || len(Password) == 0 { + log.Fatal("-apiusername and -apipassword must be used together") + } + HasAuth = true + } + if len(TLSCertFile) > 0 || len(TLSKeyFile) > 0 { + if len(TLSCertFile) == 0 || len(TLSKeyFile) == 0 { + log.Fatal("-apitlscert and -apitlskey must be used together") + } + HasTLS = true + } } func flagVar(v interface{}, name string) { @@ -452,10 +470,6 @@ func HasWhitelist() bool { return len(Whitelist) > 0 } -func HasAuth() bool { - return Username != "" && Password != "" -} - func IgnoreNewChains() bool { return ignoreNewChains || HasWhitelist() } diff --git a/srv/srv.go b/srv/srv.go index c8cbc6a..533c6e7 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -51,18 +51,23 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { // Set up JSON RPC 2.0 handler with correct headers. jrpc.DebugMethodFunc = true jrpcHandler := jrpc.HTTPRequestHandler(jrpcMethods) - var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) { - header := w.Header() - header.Add(FatdVersionHeaderKey, flag.Revision) - header.Add(FatdAPIVersionHeaderKey, APIVersion) - jrpcHandler(w, r) + + var handler http.Handler = http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + header := w.Header() + header.Add(FatdVersionHeaderKey, flag.Revision) + header.Add(FatdAPIVersionHeaderKey, APIVersion) + jrpcHandler(w, r) + }) + if flag.HasAuth { + handler = httpauth.SimpleBasicAuth(flag.Username, flag.Password)(handler) } // Set up server. srvMux := http.NewServeMux() - srvMux.Handle("/", handleAuth(handler)) - srvMux.Handle("/v1", handleAuth(handler)) + srvMux.Handle("/", handler) + srvMux.Handle("/v1", handler) cors := cors.New(cors.Options{AllowedOrigins: []string{"*"}}) srv = http.Server{Handler: cors.Handler(srvMux)} @@ -73,7 +78,13 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { _done := make(chan struct{}) log.Infof("Listening on %v...", flag.APIAddress) go func() { - if err := listenAndServe(srv); err != http.ErrServerClosed { + var err error + if flag.HasTLS { + err = srv.ListenAndServeTLS(flag.TLSCertFile, flag.TLSKeyFile) + } else { + err = srv.ListenAndServe() + } + if err != http.ErrServerClosed { log.Errorf("srv.ListenAndServe(): %v", err) } close(_done) @@ -90,20 +101,3 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { }() return _done } - -func listenAndServe(server http.Server) interface{} { - if flag.TLSEnable { - return server.ListenAndServeTLS(flag.TLSCertFile, flag.TLSKeyFile) - } else { - return server.ListenAndServe() - } - -} - -func handleAuth(handlerFunc http.HandlerFunc) http.Handler { - if flag.HasAuth() { - return (httpauth.SimpleBasicAuth(flag.Username, flag.Password))(handlerFunc) - } else { - return handlerFunc - } -} From 77df9945d98c36456873c8b1606a7f76873bc46f Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 30 Aug 2019 16:36:06 -0800 Subject: [PATCH 035/124] fix(db,srv): Catch unseen address error as txErr --- db/address.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/db/address.go b/db/address.go index f0115f1..9d7146e 100644 --- a/db/address.go +++ b/db/address.go @@ -30,6 +30,9 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, } id, err := SelectAddressID(chain.Conn, adr) if err != nil { + if err == sqlitexNoResultsErr { + return id, fmt.Errorf("insufficient balance: %v", adr), nil + } return id, nil, err } if id < 0 { @@ -51,11 +54,13 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, return id, nil, nil } +var sqlitexNoResultsErr = fmt.Errorf("sqlite: statement has no results") + func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, error) { stmt := conn.Prep(`SELECT "balance" FROM "addresses" WHERE "address" = ?;`) stmt.BindBytes(1, adr[:]) bal, err := sqlitex.ResultInt64(stmt) - if err != nil && err.Error() == "sqlite: statement has no results" { + if err != nil && err == sqlitexNoResultsErr { return 0, nil } if err != nil { From b289206dbfc8b89792749222ef6c0db251cf679a Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 30 Aug 2019 16:46:45 -0800 Subject: [PATCH 036/124] fix(db): Catch "no results" errors properly --- db/address.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/address.go b/db/address.go index 9d7146e..8c2c635 100644 --- a/db/address.go +++ b/db/address.go @@ -30,7 +30,7 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, } id, err := SelectAddressID(chain.Conn, adr) if err != nil { - if err == sqlitexNoResultsErr { + if err.Error() == sqlitexNoResultsErr { return id, fmt.Errorf("insufficient balance: %v", adr), nil } return id, nil, err @@ -54,13 +54,13 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, return id, nil, nil } -var sqlitexNoResultsErr = fmt.Errorf("sqlite: statement has no results") +var sqlitexNoResultsErr = "sqlite: statement has no results" func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, error) { stmt := conn.Prep(`SELECT "balance" FROM "addresses" WHERE "address" = ?;`) stmt.BindBytes(1, adr[:]) bal, err := sqlitex.ResultInt64(stmt) - if err != nil && err == sqlitexNoResultsErr { + if err != nil && err.Error() == sqlitexNoResultsErr { return 0, nil } if err != nil { From 90fdb9297a1c64d61805eea11b53e30d9ced7d02 Mon Sep 17 00:00:00 2001 From: Alex Carrithers <38507729+afenrir@users.noreply.github.com> Date: Fri, 30 Aug 2019 11:56:31 +0100 Subject: [PATCH 037/124] Add wait script to Docker image. --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 085582b..979ddf1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,4 +17,7 @@ FROM alpine:3.10 COPY --from=builder /go/src/github.com/Factom-Asset-Tokens/fatd/fatd . +ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait +RUN chmod +x /wait + ENTRYPOINT [ "./fatd" ] From 73c8a69abb62b097d2a5977fb14f010cba819a72 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 30 Aug 2019 17:18:34 -0800 Subject: [PATCH 038/124] fix(srv): Write `{}` back for unauthorized requests --- srv/srv.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/srv/srv.go b/srv/srv.go index 533c6e7..430892d 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -60,7 +60,16 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { jrpcHandler(w, r) }) if flag.HasAuth { - handler = httpauth.SimpleBasicAuth(flag.Username, flag.Password)(handler) + authOpts := httpauth.AuthOptions{ + User: flag.Username, + Password: flag.Password, + UnauthorizedHandler: http.HandlerFunc( + func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{}`)) + }), + } + handler = httpauth.BasicAuth(authOpts)(handler) } // Set up server. From dac135248921c875fa2022d3b2ae02a322211554 Mon Sep 17 00:00:00 2001 From: Devon Katz Date: Fri, 30 Aug 2019 19:42:02 -0700 Subject: [PATCH 039/124] Document basic auth & tls flags --- README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c481104..34db36c 100644 --- a/README.md +++ b/README.md @@ -174,20 +174,24 @@ Control how fatd runs using additional options at startup. For example: | `dbpath` | Specify the path to use as fatd's sqlite database. | Valid system path | Current working directory | | `ecpub` | The public Entry Credit address used to pay for submitting transactions | Valid EC address | - | | `apiaddress` | What port string the FAT daemon RPC will be bound to | String | `:8078` | +| `apiusername` | The basic HTTP auth username to require for connections to fatd | String | - | +| `apipassword` | The basic HTTP auth username to require for connections to fatd | String | - | +| `apitlscert` | The file path to the TLS certificate file to use for secure conncetions | Valid system path | - | +| `apitlskey` | The file path to the TLS key file to use for secure conncetions | Valid system path | - | | | | | | | `s` | The URL of the Factom API host | Valid URL | `localhost:8088` | -| `factomdtimeout` | The timeout in seconds to time out requests to factomd | integer | 0 | -| `factomduser` | The username of the user for factomd API authentication | string | - | -| `factomdpassword` | The password of the user for factomd API authentication | string | - | +| `factomdtimeout` | The timeout in seconds to time out requests to factomd | Integer | 0 | +| `factomduser` | The username of the user for factomd API authentication | String | - | +| `factomdpassword` | The password of the user for factomd API authentication | String | - | | `factomdcert` | Path to the factomd connection TLS certificate file | Valid system path string | - | -| `factomdtls` | Whether to use TLS on connection to factomd | boolean | false | +| `factomdtls` | Whether to use TLS on connection to factomd | Boolean | false | | | | | | | `w` | The URL of the Factom Wallet Daemon API host | Valid URL | `localhost:8089` | -| `wallettimeout` | The timeout in seconds to time out requests to factomd | integer | 0 | -| `walletuser` | The username of the user for walletd API authentication | string | - | -| `walletpassword` | The username of the user for walletd API authentication | string | - | +| `wallettimeout` | The timeout in seconds to time out requests to factomd | Integer | 0 | +| `walletuser` | The username of the user for walletd API authentication | String | - | +| `walletpassword` | The username of the user for walletd API authentication | String | - | | `walletcert` | Path to the walletd connection TLS certificate file | Valid system path string | - | -| `wallettls` | Whether to use TLS on connection to walletd | boolean | false | +| `wallettls` | Whether to use TLS on connection to walletd | Boolean | false | For a complete up to date list of flags & options please see `flag/flag.go` From 0a4bfd654b03a8c46fdfabffbad94450c58451f7 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 5 Sep 2019 17:23:15 -0800 Subject: [PATCH 040/124] refactor(factom): Change ChainID sort order for PendingEntries for easier exclusion of unrevealed entries --- factom/pendingentries.go | 37 +++++++++++++++++++++++------------ factom/pendingentries_test.go | 6 +++--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/factom/pendingentries.go b/factom/pendingentries.go index 12dbc90..dc7388e 100644 --- a/factom/pendingentries.go +++ b/factom/pendingentries.go @@ -32,8 +32,9 @@ import ( // and can be queried from factomd. type PendingEntries []Entry -// Get returns all pending entries sorted by ChainID, and then order they were -// originally returned. +// Get returns all pending entries sorted by descending ChainID, and then order +// they were originally returned. Pending Entries that are committed but not +// revealed have a nil ChainID and are at the end of the pe slice. func (pe *PendingEntries) Get(c *Client) error { if err := c.FactomdRequest("pending-entries", nil, pe); err != nil { return err @@ -48,7 +49,7 @@ func (pe *PendingEntries) Get(c *Client) error { if ej.ChainID != nil { cj = ej.ChainID[:] } - return bytes.Compare(ci, cj) < 0 + return bytes.Compare(ci, cj) > 0 }) return nil } @@ -56,6 +57,10 @@ func (pe *PendingEntries) Get(c *Client) error { // Entries efficiently finds and returns all entries in pe for the given // chainID, if any exist. Otherwise, Entries returns nil. func (pe PendingEntries) Entries(chainID *Bytes32) []Entry { + var cID []byte + if chainID != nil { + cID = chainID[:] + } // Find the first index of the entry with this chainID. ei := sort.Search(len(pe), func(i int) bool { var c []byte @@ -63,17 +68,23 @@ func (pe PendingEntries) Entries(chainID *Bytes32) []Entry { if e.ChainID != nil { c = e.ChainID[:] } - return bytes.Compare(c, chainID[:]) >= 0 + return bytes.Compare(c, cID) <= 0 }) - if ei < len(pe) && *pe[ei].ChainID == *chainID { - // Find all remaining entries with the chainID. - for i, e := range pe[ei:] { - if *e.ChainID != *chainID { - return pe[ei : ei+i] - } - } + if chainID == nil { + // Unrevealed entries with no ChainID are all at the end, if any. return pe[ei:] } - // There are no entries for this ChainID. - return nil + if ei == len(pe) || // (None are true) OR + // (We found nil OR we did not find an exact match) + (pe[ei].ChainID == nil || *pe[ei].ChainID != *chainID) { + // There are no entries for this ChainID. + return nil + } + // Find all remaining entries with the chainID. + for i, e := range pe[ei:] { + if e.ChainID == nil || *e.ChainID != *chainID { + return pe[ei : ei+i] + } + } + return pe[ei:] } diff --git a/factom/pendingentries_test.go b/factom/pendingentries_test.go index 2c70369..e4f7a2d 100644 --- a/factom/pendingentries_test.go +++ b/factom/pendingentries_test.go @@ -52,7 +52,7 @@ func TestPendingEntries(t *testing.T) { return } - fmt.Printf("%+v\n", pe) + fmt.Printf("pe: %+v\n", pe) require.True(sort.SliceIsSorted(pe, func(i, j int) bool { var ci, cj []byte @@ -63,7 +63,7 @@ func TestPendingEntries(t *testing.T) { if ej.ChainID != nil { cj = ej.ChainID[:] } - return bytes.Compare(ci, cj) < 0 + return bytes.Compare(ci, cj) > 0 }), "not sorted") es := pe.Entries(&Bytes32{}) @@ -83,7 +83,7 @@ func TestPendingEntries(t *testing.T) { if len(es) == 0 { return } - fmt.Printf("%+v\n", es) + fmt.Printf("es: %+v\n", es) for _, e := range es { assert.Equal(*e.ChainID, *searchID) } From 91ddd21ad3e747557e1b739683ee034c13da4f85 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 5 Sep 2019 17:24:26 -0800 Subject: [PATCH 041/124] feat(db): Add ApplyEntry function for use in pending transactions --- db/apply.go | 44 +++++++++++++++++++++++++------------------- db/chain.go | 14 +++++++++++++- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/db/apply.go b/db/apply.go index b683c35..d18be98 100644 --- a/db/apply.go +++ b/db/apply.go @@ -10,7 +10,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -type applyFunc func(*Chain, int64, factom.Entry) error +type applyFunc func(*Chain, int64, factom.Entry) (txErr, err error) func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) { // Ensure entire EBlock is applied atomically. @@ -31,19 +31,22 @@ func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) // Save and apply each entry. for _, e := range eb.Entries { - var ei int64 - ei, err = chain.InsertEntry(e, chain.Head.Sequence) - if err != nil { - return - } - if err = chain.apply(chain, ei, e); err != nil { + if _, err = chain.ApplyEntry(e); err != nil { return } } return } -var rollbackErr = fmt.Errorf("rollback") +func (chain *Chain) ApplyEntry(e factom.Entry) (txErr, err error) { + ei, err := chain.InsertEntry(e, chain.Head.Sequence) + if err != nil { + return + } + return chain.apply(chain, ei, e) +} + +var alwaysRollbackErr = fmt.Errorf("always rollback") func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error) { issuance := fat.NewIssuance(e) @@ -51,7 +54,7 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error chainCopy := *chain defer func() { if err != nil || issueErr != nil { - rollback(&rollbackErr) + rollback(&alwaysRollbackErr) // Reset chain on error *chain = chainCopy } @@ -82,23 +85,26 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error func (chain *Chain) setApplyFunc() { if !chain.Issuance.IsPopulated() { - chain.apply = func(chain *Chain, ei int64, e factom.Entry) error { - _, err := chain.applyIssuance(ei, e) - return err + chain.apply = func(chain *Chain, ei int64, e factom.Entry) ( + txErr, err error) { + txErr, err = chain.applyIssuance(ei, e) + return } return } // Adapt to match ApplyFunc. switch chain.Type { case fat0.Type: - chain.apply = func(chain *Chain, ei int64, e factom.Entry) error { - _, _, err := chain.ApplyFAT0Tx(ei, e) - return err + chain.apply = func(chain *Chain, ei int64, e factom.Entry) ( + txErr, err error) { + _, txErr, err = chain.ApplyFAT0Tx(ei, e) + return } case fat1.Type: - chain.apply = func(chain *Chain, ei int64, e factom.Entry) error { - _, _, err := chain.ApplyFAT1Tx(ei, e) - return err + chain.apply = func(chain *Chain, ei int64, e factom.Entry) ( + txErr, err error) { + _, txErr, err = chain.ApplyFAT1Tx(ei, e) + return } default: panic("invalid FAT type") @@ -111,7 +117,7 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { return func(txErr, err *error) { e := tx.FactomEntry() if *err != nil || *txErr != nil { - rollback(&rollbackErr) + rollback(&alwaysRollbackErr) // Reset chain on error *chain = chainCopy } diff --git a/db/chain.go b/db/chain.go index f694acb..a7c7cb1 100644 --- a/db/chain.go +++ b/db/chain.go @@ -215,9 +215,16 @@ func open(fname string) (chain Chain, err error) { if err = validateOrApplySchema(conn, chainDBSchema); err != nil { return } + // We only really need foreign key checks on the main database write + // connection. if err = sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { return } + + // This pool is technically RWrite to allow for the same functions to + // be used for the "send-transaction" API method. But chain.Get() + // creates a Savepoint around any connections from this pool and always + // rollsback, making this effectively a readonly connection. flags = baseFlags pool, err := sqlitex.Open(path, flags, PoolSize) if err != nil { @@ -230,6 +237,7 @@ func open(fname string) (chain Chain, err error) { }, nil } +// Close all database connections. Log any errors. func (chain *Chain) Close() { if err := chain.Pool.Close(); err != nil { chain.Log.Errorf("chain.Pool.Close(): %v", err) @@ -240,11 +248,15 @@ func (chain *Chain) Close() { } } +// Get() returns a threadsafe connection to the database, and a function to +// release the connection back to the pool. The connection allows writes but no +// writes will persist or ever be visible to any other connection as all +// changes are rolled back, making this effectively a readonly connection. func (chain *Chain) Get() (*sqlite.Conn, func()) { conn := chain.Pool.Get(nil) rollback := sqlitex.Save(conn) return conn, func() { - rollback(&rollbackErr) + rollback(&alwaysRollbackErr) chain.Pool.Put(conn) } } From 75856ff9a3a354362316224ccef139cb26e3e13d Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 10 Sep 2019 14:27:08 -0800 Subject: [PATCH 042/124] feat(engine,db,srv): Add pending transaction tracking support The long awaited pending transaction support is here. This implementation fully leverages the existing validation and database code. No new tables, nor schema changes were required. When pending transactions are enabled (default), when a pending transaction for a tracked change is detected, a new in memory copy of that chains database is created. This in memory database uses a shared cache so a pool of read only connections can also be opened to it. A session is used to track the changes to the in memory database as pending entries are applied to it. When the next EBlock is published, the inverse of the session's changeset is used to rollback all pending txs on the in memory db. A new session on the main database is used to generate a changeset for applying this EBlock. This session is kept open until another pending entry is available for that database, at which point this session's changeset is used to sync the in-memory database with the main database. One advantage to this approach is that is allows for full code reuse of the validation code. The downside is that a full copy of each chain database must be kept around in memory. The -disablepending flag has been added to allow resource constrained systems to not track pending transactions. re #14 #8 --- db/apply.go | 26 +++++----- db/chain.go | 100 ++++++++++++++++++-------------------- db/chain_test.go | 3 +- db/validate.go | 4 ++ engine/chain.go | 102 ++++++++++++++++++++++++++++++++++++++- engine/chainmap.go | 5 +- engine/engine.go | 51 ++++++++++++++++++-- engine/process.go | 105 ++++++++++++++++++++++++++++++++++++++++ flag/flag.go | 19 +++++++- go.mod | 4 +- go.sum | 8 ++-- srv/errors.go | 2 + srv/methods.go | 117 +++++++++++++++++++-------------------------- srv/params.go | 10 +++- 14 files changed, 405 insertions(+), 151 deletions(-) diff --git a/db/apply.go b/db/apply.go index d18be98..628f4d7 100644 --- a/db/apply.go +++ b/db/apply.go @@ -24,12 +24,12 @@ func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) chain.Head = eb - // Save latest EBlock. + // Insert latest EBlock. if err = chain.insertEBlock(eb, dbKeyMR); err != nil { return } - // Save and apply each entry. + // Insert each entry and attempt to apply it... for _, e := range eb.Entries { if _, err = chain.ApplyEntry(e); err != nil { return @@ -57,15 +57,14 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error rollback(&alwaysRollbackErr) // Reset chain on error *chain = chainCopy - } - if err != nil { - return - } - if issueErr != nil { - chain.Log.Debugf("Entry{%v}: invalid issuance: %v", + if err != nil { + return + } + chain.Log.Debugf("Entry{%v}: invalid Issuance: %v", e.Hash, issueErr) return } + rollback(&err) // commit chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) }() // The Identity must exist prior to issuance. @@ -120,15 +119,14 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { rollback(&alwaysRollbackErr) // Reset chain on error *chain = chainCopy - } - if *err != nil { - return - } - if *txErr != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + if *err != nil { + return + } + chain.Log.Debugf("Entry{%v}: invalid %v Transaction: %v", e.Hash, chain.Type, *txErr) return } + rollback(err) var cbStr string if tx.IsCoinbase() { cbStr = "Coinbase " diff --git a/db/chain.go b/db/chain.go index a7c7cb1..4b2e97a 100644 --- a/db/chain.go +++ b/db/chain.go @@ -27,13 +27,13 @@ import ( "io/ioutil" "os" "strings" + //"strings" "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/flag" _log "github.com/Factom-Asset-Tokens/fatd/log" ) @@ -64,6 +64,7 @@ type Chain struct { fat.Issuance NumIssued uint64 + DBFile string *sqlite.Conn // Read/Write *sqlitex.Pool // Read Only Pool Log _log.Log @@ -71,10 +72,12 @@ type Chain struct { apply applyFunc } -func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, +// dbPath must be path ending in os.Separator +func OpenNew(dbPath string, + dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, identity factom.Identity) (chain Chain, err error) { fname := eb.ChainID.String() + dbFileExtension - path := flag.DBPath + "/" + fname + path := dbPath + fname nameIDs := eb.Entries[0].ExtIDs if !fat.ValidTokenNameIDs(nameIDs) { @@ -92,7 +95,7 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network return } - chain, err = open(fname) + chain.Conn, chain.Pool, err = OpenConnPool(dbPath + fname) if err != nil { return } @@ -104,6 +107,8 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network } } }() + chain.Log = _log.New("chain", strings.TrimRight(fname, dbFileExtension)) + chain.DBFile = fname chain.ID = eb.ChainID chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) chain.DBKeyMR = dbKeyMR @@ -130,28 +135,25 @@ func OpenNew(dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.Network return } -func Open(fname string) (chain Chain, err error) { - chain, err = open(fname) +func Open(dbPath, fname string) (chain Chain, err error) { + chain.Conn, chain.Pool, err = OpenConnPool(dbPath + fname) if err != nil { return } - if err = chain.loadMetadata(); err != nil { - return - } + defer func() { + if err != nil { + chain.Close() + } + }() + chain.Log = _log.New("chain", strings.TrimRight(fname, dbFileExtension)) + chain.DBFile = fname + + err = chain.loadMetadata() return } -func OpenAll() (chains []Chain, err error) { +func OpenAll(dbPath string) (chains []Chain, err error) { log = _log.New("pkg", "db") - // Try to create the database directory in case it doesn't already - // exist. - if err := os.Mkdir(flag.DBPath, 0755); err != nil { - if !os.IsExist(err) { - return nil, fmt.Errorf("os.Mkdir(%#v): %v", flag.DBPath, err) - } - log.Debug("Using existing database directory...") - } - defer func() { if err != nil { for _, chain := range chains { @@ -163,9 +165,9 @@ func OpenAll() (chains []Chain, err error) { // Scan through all files within the database directory. Ignore invalid // file names. - files, err := ioutil.ReadDir(flag.DBPath) + files, err := ioutil.ReadDir(dbPath) if err != nil { - return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) + return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", dbPath, err) } chains = make([]Chain, 0, len(files)) for _, f := range files { @@ -175,14 +177,16 @@ func OpenAll() (chains []Chain, err error) { continue } log.Debugf("Loading chain: %v", chainID) - chain, err := Open(fname) + chain, err := Open(dbPath, fname) if err != nil { return nil, err } + chains = append(chains, chain) if *chainID != *chain.ID { - return nil, fmt.Errorf("chain id does not match filename") + return nil, fmt.Errorf( + "filename %v does not match database Chain ID %v", + fname, chain.ID) } - chains = append(chains, chain) } return chains, nil } @@ -200,41 +204,37 @@ func fnameToChainID(fname string) (*factom.Bytes32, error) { return chainID, nil } -func open(fname string) (chain Chain, err error) { +func OpenConnPool(dbURI string) (conn *sqlite.Conn, pool *sqlitex.Pool, + err error) { const baseFlags = sqlite.SQLITE_OPEN_WAL | sqlite.SQLITE_OPEN_URI | - sqlite.SQLITE_OPEN_NOMUTEX | - sqlite.SQLITE_OPEN_READWRITE - path := flag.DBPath + "/" + fname - flags := baseFlags | sqlite.SQLITE_OPEN_CREATE - conn, err := sqlite.OpenConn(path, flags) - if err != nil { - err = fmt.Errorf("sqlite.OpenConn(%q, %x): %v", path, flags, err) + sqlite.SQLITE_OPEN_NOMUTEX + flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE + if conn, err = sqlite.OpenConn(dbURI, flags); err != nil { + err = fmt.Errorf("sqlite.OpenConn(%q, %x): %v", dbURI, flags, err) return } + defer func() { + if err != nil { + if err := conn.Close(); err != nil { + log.Error(err) + } + } + }() if err = validateOrApplySchema(conn, chainDBSchema); err != nil { return } - // We only really need foreign key checks on the main database write - // connection. if err = sqlitex.ExecScript(conn, `PRAGMA foreign_keys = ON;`); err != nil { return } - // This pool is technically RWrite to allow for the same functions to - // be used for the "send-transaction" API method. But chain.Get() - // creates a Savepoint around any connections from this pool and always - // rollsback, making this effectively a readonly connection. - flags = baseFlags - pool, err := sqlitex.Open(path, flags, PoolSize) - if err != nil { + flags = baseFlags | sqlite.SQLITE_OPEN_READWRITE + if pool, err = sqlitex.Open(dbURI, flags, PoolSize); err != nil { err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", - path, flags, PoolSize, err) + dbURI, flags, PoolSize, err) return } - return Chain{Conn: conn, Pool: pool, - Log: _log.New("chain", strings.TrimRight(fname, dbFileExtension)), - }, nil + return } // Close all database connections. Log any errors. @@ -249,14 +249,8 @@ func (chain *Chain) Close() { } // Get() returns a threadsafe connection to the database, and a function to -// release the connection back to the pool. The connection allows writes but no -// writes will persist or ever be visible to any other connection as all -// changes are rolled back, making this effectively a readonly connection. +// release the connection back to the pool. func (chain *Chain) Get() (*sqlite.Conn, func()) { conn := chain.Pool.Get(nil) - rollback := sqlitex.Save(conn) - return conn, func() { - rollback(&alwaysRollbackErr) - chain.Pool.Put(conn) - } + return conn, func() { chain.Pool.Put(conn) } } diff --git a/db/chain_test.go b/db/chain_test.go index 7511c5e..b6bda20 100644 --- a/db/chain_test.go +++ b/db/chain_test.go @@ -10,9 +10,8 @@ import ( func TestChainValidate(t *testing.T) { require := require.New(t) - flag.DBPath = "./test-fatd.db" flag.LogDebug = true - chains, err := OpenAll() + chains, err := OpenAll("./test-fatd.db/") require.NoError(err, "OpenAll()") require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) diff --git a/db/validate.go b/db/validate.go index ce8a164..400370d 100644 --- a/db/validate.go +++ b/db/validate.go @@ -12,6 +12,10 @@ import ( "github.com/Factom-Asset-Tokens/fatd/flag" ) +func init() { + sqlitechangeset.AlwaysUseBlob = true +} + // ValidateChain validates all Entry Hashes and EBlock KeyMRs, as well as the // continuity of all stored EBlocks and Entries. It does not validate the // validity of the saved DBlock KeyMRs. diff --git a/engine/chain.go b/engine/chain.go index 5dbb4ec..def441d 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -23,9 +23,13 @@ package engine import ( + "bytes" "fmt" + "crawshaw.io/sqlite" + jrpc "github.com/AdamSLevy/jsonrpc2/v11" + "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat" @@ -35,6 +39,79 @@ import ( type Chain struct { ChainStatus db.Chain + + Pending Pending +} + +type Pending struct { + PendSess *sqlite.Session + MainSess *sqlite.Session + Chain db.Chain + Entries map[factom.Bytes32]factom.Entry +} + +func (p *Pending) Close() { + p.DeleteSessions() + p.Chain.Close() +} + +func (p *Pending) DeleteSessions() { + if p.PendSess != nil { + p.PendSess.Delete() + p.PendSess = nil + } + if p.MainSess != nil { + p.MainSess.Delete() + p.MainSess = nil + } +} + +func (p *Pending) Sync(chain db.Chain) error { + // Reset p.Chain to chain, but preserve the existing Conn and Pool. + conn, pool := p.Chain.Conn, p.Chain.Pool + p.Chain = chain + p.Chain.Conn, p.Chain.Pool = conn, pool + + p.Entries = nil + + // Ensure the sessions are deleted and freed. + defer p.DeleteSessions() + + if p.PendSess != nil { + // Revert all of the pending transactions by applying the inverse of + // the changeset tracked by session. + changeset := &bytes.Buffer{} + if err := p.PendSess.Changeset(changeset); err != nil { + return err + } + inverse := bytes.NewBuffer(make([]byte, 0, changeset.Cap())) + if err := sqlite.ChangesetInvert(inverse, changeset); err != nil { + return err + } + conflictFn := func(cType sqlite.ConflictType, + _ sqlite.ChangesetIter) sqlite.ConflictAction { + chain.Log.Errorf("ChangesetApply Conflict: %v", cType) + return sqlite.SQLITE_CHANGESET_ABORT + } + if err := p.Chain.Conn.ChangesetApply(inverse, nil, conflictFn); err != nil { + return err + } + chain.Log.Debug("apply PendSess") + } + + if p.MainSess != nil { + // Apply all of the official transactions. + changeset := &bytes.Buffer{} + if err := p.MainSess.Changeset(changeset); err != nil { + return err + } + if err := p.Chain.Conn.ChangesetApply(changeset, nil, nil); err != nil { + return err + } + chain.Log.Debug("apply MainSess") + } + + return nil } func (chain Chain) String() string { @@ -50,7 +127,7 @@ func OpenNew(c *factom.Client, return chain, fmt.Errorf("%#v.Get(c): %v", eb, err) } // Load first entry of new chain. - first := eb.Entries[0] + first := &eb.Entries[0] if err := first.Get(c); err != nil { return chain, fmt.Errorf("%#v.Get(c): %v", first, err) } @@ -78,7 +155,7 @@ func OpenNew(c *factom.Client, return chain, fmt.Errorf("%#v.GetEntries(c): %v", eb, err) } - chain.Chain, err = db.OpenNew(dbKeyMR, eb, flag.NetworkID, identity) + chain.Chain, err = db.OpenNew(flag.DBPath, dbKeyMR, eb, flag.NetworkID, identity) if err != nil { return chain, fmt.Errorf("db.OpenNew(): %v", err) } @@ -87,6 +164,7 @@ func OpenNew(c *factom.Client, } else { chain.ChainStatus = ChainStatusTracked } + chain.Pending.Chain = chain.Chain return } @@ -167,3 +245,23 @@ func (chain *Chain) Apply(c *factom.Client, } return nil } + +func (p *Pending) Open(chain db.Chain) (err error) { + dbURI := fmt.Sprintf("file:%v?mode=memory&cache=shared", chain.ID) + conn, err := chain.Conn.BackupToDB("", dbURI) + defer func() { + if err != nil { + conn.Close() + } + }() + + // Open the pending database connection and pool + c, pool, err := db.OpenConnPool(dbURI) + if err != nil { + return + } + c.Close() // Redundant connection... + p.Chain = chain + p.Chain.Conn, p.Chain.Pool = conn, pool + return +} diff --git a/engine/chainmap.go b/engine/chainmap.go index ccd2c29..ddfcc08 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -108,6 +108,9 @@ func (cm ChainMap) Close() { cm.Lock() for _, chain := range cm.m { if chain.IsTracked() { + if chain.Pending.Chain.Conn != nil { + chain.Pending.Close() + } chain.Close() } } @@ -117,7 +120,7 @@ func (cm ChainMap) Close() { // syncs them. Any whitelisted chains that are not previously tracked are // synced. The lowest sync height among all chain databases is returned. func loadChains() (syncHeight uint32, err error) { - dbChains, err := db.OpenAll() + dbChains, err := db.OpenAll(flag.DBPath) if err != nil { return } diff --git a/engine/engine.go b/engine/engine.go index 38280f0..875f43e 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -24,7 +24,7 @@ package engine import ( "fmt" - "path/filepath" + "os" "sync" "time" @@ -53,15 +53,23 @@ const ( func Start(stop <-chan struct{}) (done <-chan struct{}) { log = _log.New("pkg", "engine") + // Try to create the main and pending database directories, in case + // they don't already exist. + if err := createDir(flag.DBPath); err != nil { + log.Error(err) + return + } + // Try to create a lockfile - lockFilePath, err := filepath.Abs(flag.DBPath + "/db.lock") + lockFilePath := flag.DBPath + "db.lock" + var err error lockFile, err = lockfile.New(lockFilePath) if err != nil { log.Error(err) return } if err = lockFile.TryLock(); err != nil { - log.Errorf("Database in use by other process.") + log.Errorf("Database in use by other process. %v", err) return } defer func() { @@ -240,6 +248,34 @@ func engine(stop <-chan struct{}, done chan struct{}) { } if synced { + var pe factom.PendingEntries + if flag.DisablePending { + goto WAIT + } + // Get and apply any pending entries + if err := pe.Get(c); err != nil { + log.Error(err) + return + } + for i, j := 0, 0; i < len(pe); i = j { + e := pe[i] + if e.ChainID == nil { + // No more revealed entries + break + } + // Grab remaining entries with this chain ID. + for j = i + 1; j < len(pe); j++ { + chainID := pe[j].ChainID + if chainID == nil || *chainID != *e.ChainID { + break + } + } + if err := ProcessPending(pe[i:j]...); err != nil { + log.Error(err) + return + } + } + WAIT: // Wait until the next scan tick or we're told to stop. select { case <-scanTicker.C: @@ -298,3 +334,12 @@ func launchWorkers(num int, job func()) { go job() } } + +func createDir(path string) error { + if err := os.Mkdir(path, 0755); err != nil { + if !os.IsExist(err) { + return fmt.Errorf("os.Mkdir(%#v): %v", path, err) + } + } + return nil +} diff --git a/engine/process.go b/engine/process.go index 387e295..9ae10e6 100644 --- a/engine/process.go +++ b/engine/process.go @@ -23,6 +23,9 @@ package engine import ( + "time" + + jrpc "github.com/AdamSLevy/jsonrpc2/v11" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/flag" ) @@ -57,6 +60,31 @@ func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { if eb.Height <= chain.Head.Height { return nil } + if chain.Pending.Entries != nil { + // Load any cached entries that are pending. + for i := range eb.Entries { + e := &eb.Entries[i] + cached, ok := chain.Pending.Entries[*e.Hash] + if !ok { + continue + } + // Save Timestamp established by EBlock + cached.Timestamp = e.Timestamp + *e = cached + } + chain.Log.Debug("main sync") + if err := chain.Pending.Sync(chain.Chain); err != nil { + return err + } + session, err := chain.Conn.CreateSession("") + if err != nil { + return err + } + if err := session.Attach(""); err != nil { + return err + } + chain.Pending.MainSess = session + } prevStatus := chain.ChainStatus if err := chain.Apply(c, dbKeyMR, eb); err != nil { return err @@ -64,3 +92,80 @@ func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { Chains.set(chain.ID, chain, prevStatus) return nil } + +func ProcessPending(es ...factom.Entry) error { + e := es[0] // Deliberately panic if we are called with no entries. + if e.ChainID == nil { + return nil + } + chain := Chains.Get(e.ChainID) + // We can only apply pending entries to tracked chains. + if !chain.IsTracked() { + return nil + } + + if chain.Pending.Entries == nil { + if chain.Pending.Chain.Conn == nil { + if err := chain.Pending.Open(chain.Chain); err != nil { + return err + } + } else { + if err := chain.Pending.Sync(chain.Chain); err != nil { + return err + } + } + + chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) + + session, err := chain.Pending.Chain.Conn.CreateSession("") + if err != nil { + return err + } + if err := session.Attach(""); err != nil { + return err + } + chain.Pending.PendSess = session + + // Small chance Identity is populated now but wasn't before... + if err := chain.Identity.Get(c); err != nil { + // A jrpc.Error indicates that the identity chain doesn't yet + // exist, which we tolerate. + if _, ok := err.(jrpc.Error); !ok { + return err + } + } + } + + lenEntries := len(chain.Pending.Entries) + for _, e := range es { + if _, ok := chain.Pending.Entries[*e.Hash]; ok { + // Ignore entries we have seen before. + continue + } + if err := e.Get(c); err != nil { + return err + } + // The timestamp will later be established by the next EBlock so use + // the current time for now. This is the time we first saw the pending + // entry. + e.Timestamp = time.Now() + if _, err := chain.Pending.Chain.ApplyEntry(e); err != nil { + return err + } + chain.Pending.Entries[*e.Hash] = e // Cache the entry in memory. + } + if lenEntries == len(chain.Pending.Entries) { + // No new entries + return nil + } + Chains.set(chain.ID, chain, chain.ChainStatus) + return nil +} + +// ApplyPending applies any pending txs to conn. +func (chain *Chain) ApplyPending() { + if chain.Pending.Chain.Conn == nil || chain.Pending.MainSess != nil { + return + } + chain.Chain = chain.Pending.Chain +} diff --git a/flag/flag.go b/flag/flag.go index 01ecf28..cd3a7b2 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -26,6 +26,7 @@ import ( "flag" "fmt" "os" + "path/filepath" "strconv" "time" @@ -44,6 +45,7 @@ var ( "startscanheight": "START_SCAN_HEIGHT", "factomscanretries": "FACTOM_SCAN_RETRIES", "debug": "DEBUG", + "disablepending": "DISABLE_PENDING", "dbpath": "DB_PATH", @@ -81,6 +83,7 @@ var ( "startscanheight": uint64(0), "factomscanretries": int64(0), "debug": false, + "disablepending": false, "dbpath": "./fatd.db", @@ -114,6 +117,7 @@ var ( "startscanheight": "Block height to start scanning for deposits on startup", "factomscanretries": "Number of times to consecutively retry fetching the latest height before exiting, use -1 for unlimited", "debug": "Log debug messages", + "disablepending": "Do not scan for pending txs, reducing memory usage", "dbpath": "Path to the folder containing all database files", @@ -141,7 +145,7 @@ var ( "ecadr": "Entry Credit Public Address to use to pay for Factom entries", "esadr": "Entry Credit Secret Address to use to pay for Factom entries", - "whitelist": "Track only these chains, adding the database if needed", + "whitelist": "Track only these chains, creating the database if needed", "blacklist": "Do not track or sync these chains, overrides -whitelist", "ignorenewchains": "Do not track new chains, sync existing chain databases", "skipdbvalidation": "Skip the full validation check of all chain databases", @@ -150,6 +154,7 @@ var ( "-startscanheight": complete.PredictAnything, "-factomscanretries": complete.PredictAnything, "-debug": complete.PredictNothing, + "-disablepending": complete.PredictNothing, "-dbpath": complete.PredictFiles("*"), @@ -197,6 +202,7 @@ var ( startScanHeight uint64 // We parse the flag as unsigned. StartScanHeight int32 = -1 // We work with the signed value. LogDebug bool + DisablePending bool FactomScanRetries int64 = -1 EsAdr factom.EsAddress @@ -230,6 +236,7 @@ func init() { flagVar(&startScanHeight, "startscanheight") flagVar(&FactomScanRetries, "factomscanretries") flagVar(&LogDebug, "debug") + flagVar(&DisablePending, "disablepending") flagVar(&DBPath, "dbpath") @@ -282,6 +289,7 @@ func Parse() { loadFromEnv(&startScanHeight, "startscanheight") loadFromEnv(&FactomScanRetries, "factomscanretries") loadFromEnv(&LogDebug, "debug") + loadFromEnv(&DisablePending, "disablepending") loadFromEnv(&DBPath, "dbpath") @@ -351,6 +359,13 @@ func Validate() { log.Debugf("-apitlskey %#v", TLSKeyFile) debugPrintln() + var err error + DBPath, err = filepath.Abs(DBPath) + if err != nil { + log.Fatalf("-dbpath %v: %v", DBPath, err) + } + DBPath += fmt.Sprintf("%c", filepath.Separator) + if factom.Bytes32(EsAdr).IsZero() { EsAdr, _ = ECAdr.GetEsAddress(FactomClient) } else { @@ -467,7 +482,7 @@ func setupLogger() { } func HasWhitelist() bool { - return len(Whitelist) > 0 + return flagset["whitelist"] } func IgnoreNewChains() bool { diff --git a/go.mod b/go.mod index 7d21660..f84da4d 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 - github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 + github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect @@ -32,4 +32,4 @@ replace github.com/gocraft/dbr => github.com/AdamSLevy/dbr v0.0.0-20190429075658 replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455 diff --git a/go.sum b/go.sum index d4c3a3a..7626e36 100644 --- a/go.sum +++ b/go.sum @@ -7,10 +7,10 @@ github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 h1:McSW/pP7K0/Ucjig6AJwW7Khph/XOMYhSB8 github.com/AdamSLevy/jsonrpc2/v11 v11.3.2/go.mod h1:7fNjH6BXM0KVswWqj+K/mnOS8wiSke0sE8X46hS+nsc= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c h1:n+ha40YmIj3rnMvg4KkNUmPRHC+U0axIZFe51LVJdaM= -github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 h1:2gBp1whWHlQvpYS9vdlmGKYBvs1qwzrxmov8KjUnBGE= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= +github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455 h1:f3m3JW4YkEDN62N036HWMFbLgT+wVgqd5UUunixT2PQ= +github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e h1:wRP8CEe6U4Q4tSD0GWUUoQ0uICteUsjD4nNkvFms2k8= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e/go.mod h1:AL34KMJ3RXL3U2gX6MWurmneH0u/ergvXeVTsUBufcc= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= diff --git a/srv/errors.go b/srv/errors.go index 32a183b..6775d65 100644 --- a/srv/errors.go +++ b/srv/errors.go @@ -34,4 +34,6 @@ var ( "token is in the process of syncing") ErrorNoEC = jrpc.NewError(-32806, "No Entry Credits", "not configured with entry credits") + ErrorPendingDisabled = jrpc.NewError(-32807, "Pending Transactions Disabled", + "fatd is not tracking pending transactions") ) diff --git a/srv/methods.go b/srv/methods.go index 068233a..91fbd35 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -71,10 +71,11 @@ type ResultGetIssuance struct { func getIssuance(entry bool) jrpc.MethodFunc { return func(data json.RawMessage) interface{} { params := ParamsToken{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } + defer put() if entry { return chain.Issuance.Entry.Entry @@ -101,15 +102,13 @@ type ResultGetTransaction struct { func getTransaction(getEntry bool) jrpc.MethodFunc { return func(data json.RawMessage) interface{} { params := ParamsGetTransaction{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } - - conn, put := chain.Get() defer put() - entry, err := db.SelectEntryByHashValid(conn, params.Hash) + entry, err := db.SelectEntryByHashValid(chain.Conn, params.Hash) if err != nil { panic(err) } @@ -151,10 +150,11 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { func getTransactions(getEntry bool) jrpc.MethodFunc { return func(data json.RawMessage) interface{} { params := ParamsGetTransactions{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } + defer put() if params.NFTokenID != nil && chain.Type != fat1.Type { err := ErrorTokenNotFound @@ -162,15 +162,12 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { return err } - conn, put := chain.Get() - defer put() - // Lookup Txs var nfTkns fat1.NFTokens if params.NFTokenID != nil { nfTkns, _ = fat1.NewNFTokens(params.NFTokenID) } - entries, err := db.SelectEntryByAddress(conn, params.StartHash, + entries, err := db.SelectEntryByAddress(chain.Conn, params.StartHash, params.Addresses, nfTkns, params.ToFrom, params.Order, *params.Page, params.Limit) @@ -223,14 +220,13 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { func getBalance(data json.RawMessage) interface{} { params := ParamsGetBalance{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } - - conn, put := chain.Get() defer put() - balance, err := db.SelectAddressBalance(conn, params.Address) + + balance, err := db.SelectAddressBalance(chain.Conn, params.Address) if err != nil { panic(err) } @@ -264,7 +260,7 @@ func (r *ResultGetBalances) UnmarshalJSON(data []byte) error { func getBalances(data json.RawMessage) interface{} { params := ParamsGetBalances{} - if _, err := validate(data, ¶ms); err != nil { + if _, _, err := validate(data, ¶ms); err != nil { return err } @@ -272,6 +268,9 @@ func getBalances(data json.RawMessage) interface{} { balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) + if params.HasIncludePending() { + chain.ApplyPending() + } conn, put := chain.Get() defer put() balance, err := db.SelectAddressBalance(conn, params.Address) @@ -287,10 +286,11 @@ func getBalances(data json.RawMessage) interface{} { func getNFBalance(data json.RawMessage) interface{} { params := ParamsGetNFBalance{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } + defer put() if chain.Type != fat1.Type { err := ErrorTokenNotFound @@ -298,9 +298,7 @@ func getNFBalance(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() - tkns, err := db.SelectNFTokensByOwner(conn, params.Address, + tkns, err := db.SelectNFTokensByOwner(chain.Conn, params.Address, *params.Page, params.Limit, params.Order) if err != nil { panic(err) @@ -331,25 +329,23 @@ var coinbaseRCDHash = fat.Coinbase() func getStats(data json.RawMessage) interface{} { params := ParamsToken{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } - - conn, put := chain.Get() defer put() - burned, err := db.SelectAddressBalance(conn, &coinbaseRCDHash) + burned, err := db.SelectAddressBalance(chain.Conn, &coinbaseRCDHash) if err != nil { panic(err) } - txCount, err := db.SelectEntryCount(conn, true) - e, err := db.SelectEntryLatestValid(conn) + txCount, err := db.SelectEntryCount(chain.Conn, true) + e, err := db.SelectEntryLatestValid(chain.Conn) if err != nil { panic(err) } - nonZeroBalances, err := db.SelectAddressCount(conn, true) + nonZeroBalances, err := db.SelectAddressCount(chain.Conn, true) if err != nil { panic(err) } @@ -382,10 +378,11 @@ type ResultGetNFToken struct { func getNFToken(data json.RawMessage) interface{} { params := ParamsGetNFToken{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } + defer put() if chain.Type != fat1.Type { err := ErrorTokenNotFound @@ -393,10 +390,7 @@ func getNFToken(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() - - owner, creationHash, metadata, err := db.SelectNFToken(conn, *params.NFTokenID) + owner, creationHash, metadata, err := db.SelectNFToken(chain.Conn, *params.NFTokenID) if err != nil { panic(err) } @@ -422,10 +416,11 @@ func getNFToken(data json.RawMessage) interface{} { func getNFTokens(data json.RawMessage) interface{} { params := ParamsGetAllNFTokens{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } + defer put() if chain.Type != fat1.Type { err := ErrorTokenNotFound @@ -433,10 +428,7 @@ func getNFTokens(data json.RawMessage) interface{} { return err } - conn, put := chain.Get() - defer put() - - tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(conn, + tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(chain.Conn, params.Order, *params.Page, params.Limit) if err != nil { panic(err) @@ -459,34 +451,17 @@ func getNFTokens(data json.RawMessage) interface{} { func sendTransaction(data json.RawMessage) interface{} { params := ParamsSendTransaction{} - chain, err := validate(data, ¶ms) + chain, put, err := validate(data, ¶ms) if err != nil { return err } + defer put() if !params.DryRun && factom.Bytes32(flag.EsAdr).IsZero() { return ErrorNoEC } - conn, put := chain.Get() - defer put() - - // Replace the conn - chain.Conn = conn - entry := params.Entry() - var ei int64 - ei, err = chain.InsertEntry(entry, chain.Head.Sequence) - if err != nil { - panic(err) - } - - var txErr error - switch chain.Type { - case fat0.Type: - _, txErr, err = chain.ApplyFAT0Tx(ei, entry) - case fat1.Type: - _, txErr, err = chain.ApplyFAT1Tx(ei, entry) - } + txErr, err := chain.ApplyEntry(entry) if err != nil { panic(err) } @@ -525,7 +500,7 @@ func sendTransaction(data json.RawMessage) interface{} { } func getDaemonTokens(data json.RawMessage) interface{} { - if _, err := validate(data, nil); err != nil { + if _, _, err := validate(data, nil); err != nil { return err } @@ -548,7 +523,7 @@ type ResultGetDaemonProperties struct { } func getDaemonProperties(data json.RawMessage) interface{} { - if _, err := validate(data, nil); err != nil { + if _, _, err := validate(data, nil); err != nil { return err } return ResultGetDaemonProperties{ @@ -568,31 +543,39 @@ func getSyncStatus(data json.RawMessage) interface{} { return ResultGetSyncStatus{Sync: sync, Current: current} } -func validate(data json.RawMessage, params Params) (*engine.Chain, error) { +func validate(data json.RawMessage, params Params) (*engine.Chain, func(), error) { if params == nil { if len(data) > 0 { - return nil, jrpc.InvalidParams(`no "params" accepted`) + return nil, nil, jrpc.InvalidParams(`no "params" accepted`) } - return nil, nil + return nil, nil, nil } if len(data) == 0 { - return nil, params.IsValid() + return nil, nil, params.IsValid() } if err := unmarshalStrict(data, params); err != nil { - return nil, jrpc.InvalidParams(err.Error()) + return nil, nil, jrpc.InvalidParams(err.Error()) } if err := params.IsValid(); err != nil { - return nil, err + return nil, nil, err + } + if params.HasIncludePending() && flag.DisablePending { + return nil, nil, ErrorPendingDisabled } chainID := params.ValidChainID() if chainID != nil { chain := engine.Chains.Get(chainID) if !chain.IsIssued() { - return nil, ErrorTokenNotFound + return nil, nil, ErrorTokenNotFound + } + if params.HasIncludePending() { + chain.ApplyPending() } - return &chain, nil + conn, put := chain.Get() + chain.Conn = conn + return &chain, put, nil } - return nil, nil + return nil, nil, nil } func unmarshalStrict(data []byte, v interface{}) error { diff --git a/srv/params.go b/srv/params.go index 1ef8163..e079a6e 100644 --- a/srv/params.go +++ b/srv/params.go @@ -35,6 +35,7 @@ import ( type Params interface { IsValid() error ValidChainID() *factom.Bytes32 + HasIncludePending() bool } // ParamsToken scopes a request down to a single FAT token using either the @@ -43,6 +44,8 @@ type ParamsToken struct { ChainID *factom.Bytes32 `json:"chainid,omitempty"` TokenID string `json:"tokenid,omitempty"` IssuerChainID *factom.Bytes32 `json:"issuerid,omitempty"` + + IncludePending bool `json:"includepending,omitempty"` } func (p ParamsToken) IsValid() error { @@ -68,6 +71,8 @@ func (p ParamsToken) IsValid() error { `required: either "chainid" or both "tokenid" and "issuerid"`) } +func (p ParamsToken) HasIncludePending() bool { return p.IncludePending } + func (p ParamsToken) ValidChainID() *factom.Bytes32 { if p.ChainID != nil { return p.ChainID @@ -189,9 +194,12 @@ func (p ParamsGetBalance) IsValid() error { } type ParamsGetBalances struct { - Address *factom.FAAddress `json:"address,omitempty"` + Address *factom.FAAddress `json:"address,omitempty"` + IncludePending bool `json:"includepending,omitempty"` } +func (p ParamsGetBalances) HasIncludePending() bool { return p.IncludePending } + func (p ParamsGetBalances) IsValid() error { if p.Address == nil { return jrpc.InvalidParams(`required: "address"`) From 829babb2d655959668fbf2226613a7af2214664f Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 10 Sep 2019 15:58:46 -0800 Subject: [PATCH 043/124] feat(cli): Support for pending transactions, other cleanup --- cli/cmd/getbalance.go | 3 ++ cli/cmd/getchains.go | 3 +- cli/cmd/gettransactions.go | 21 +++++++---- cli/cmd/root.go | 4 ++ db/apply.go | 12 +++--- db/chain.go | 7 ++++ engine/chain.go | 4 +- engine/engine.go | 2 +- engine/process.go | 1 - fat/entry.go | 1 + fat/fat0/transaction.go | 4 +- fat/fat1/transaction.go | 4 +- srv/methods.go | 76 +++++++++++++++++--------------------- 13 files changed, 77 insertions(+), 65 deletions(-) diff --git a/cli/cmd/getbalance.go b/cli/cmd/getbalance.go index a7dde34..507a9b1 100644 --- a/cli/cmd/getbalance.go +++ b/cli/cmd/getbalance.go @@ -98,6 +98,7 @@ func getBalanceArgs(cmd *cobra.Command, args []string) error { func getBalance(cmd *cobra.Command, _ []string) { if paramsToken.ChainID == nil { var params srv.ParamsGetBalances + params.IncludePending = paramsToken.IncludePending vrbLog.Println("Fetching balances for all chains...") for _, adr := range addresses { params.Address = &adr @@ -128,6 +129,7 @@ func getBalance(cmd *cobra.Command, _ []string) { case fat0.Type: params := srv.ParamsGetBalance{} params.ChainID = paramsToken.ChainID + params.IncludePending = paramsToken.IncludePending vrbLog.Println("Fetching balances...") for _, adr := range addresses { params.Address = &adr @@ -142,6 +144,7 @@ func getBalance(cmd *cobra.Command, _ []string) { var params srv.ParamsGetNFBalance params.Limit = math.MaxUint64 params.ChainID = paramsToken.ChainID + params.IncludePending = paramsToken.IncludePending vrbLog.Println("Fetching NF balances...") for _, adr := range addresses { params.Address = &adr diff --git a/cli/cmd/getchains.go b/cli/cmd/getchains.go index 5e75da4..9da8e1d 100644 --- a/cli/cmd/getchains.go +++ b/cli/cmd/getchains.go @@ -115,7 +115,8 @@ Token ID: %q for _, chainID := range chainIDs { vrbLog.Printf("Fetching token chain details... %v", chainID) - params := srv.ParamsToken{ChainID: &chainID} + params := srv.ParamsToken{ChainID: &chainID, + IncludePending: paramsToken.IncludePending} var stats srv.ResultGetStats if err := FATClient.Request("get-stats", params, &stats); err != nil { errLog.Fatal(err) diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index 0ed0baf..7fd71ac 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -26,6 +26,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" @@ -37,10 +38,11 @@ import ( var ( paramsGetTxs = srv.ParamsGetTransactions{ - StartHash: new(factom.Bytes32), - NFTokenID: new(fat1.NFTokenID), - ParamsToken: srv.ParamsToken{ChainID: paramsToken.ChainID}, - ParamsPagination: srv.ParamsPagination{Page: new(uint64)}, + StartHash: new(factom.Bytes32), + NFTokenID: new(fat1.NFTokenID), + ParamsToken: srv.ParamsToken{ChainID: paramsToken.ChainID}, + ParamsPagination: srv.ParamsPagination{Page: new(uint64), + Order: "desc"}, } to, from bool transactionIDs []factom.Bytes32 @@ -84,7 +86,7 @@ more, and in the case of a FAT-1 chain, by a single --nftokenid. Use --page and flags.Uint64VarP(paramsGetTxs.Page, "page", "p", 1, "Page of returned txs") flags.Uint64VarP(¶msGetTxs.Limit, "limit", "l", 10, "Limit of returned txs") flags.VarPF((*txOrder)(¶msGetTxs.Order), "order", "", "Order of returned txs"). - DefValue = "asc" + DefValue = "desc" flags.BoolVar(&to, "to", false, "Request only txs TO the given --address set") flags.BoolVar(&from, "from", false, "Request only txs FROM the given --address set") flags.VarPF(paramsGetTxs.StartHash, "starttx", "", @@ -128,6 +130,7 @@ func validateGetTxsFlags(cmd *cobra.Command, args []string) error { if err := validateChainIDFlags(cmd, args); err != nil { return err } + paramsGetTxs.IncludePending = paramsToken.IncludePending flags := cmd.LocalFlags() if len(transactionIDs) > 0 { for _, flgName := range []string{"page", "order", "page", "limit", @@ -208,9 +211,13 @@ func getTxs(_ *cobra.Command, _ []string) { } func printTx(result srv.ResultGetTransaction) { + if result.Pending { + fmt.Println("PENDING TX") + } fmt.Println("TXID:", result.Hash) - fmt.Println("Timestamp:", result.Timestamp) + fmt.Println("Timestamp:", time.Unix(result.Timestamp, 0)) fmt.Println("TX:", (string)(*result.Tx.(*json.RawMessage))) + fmt.Println() } @@ -238,7 +245,7 @@ func (o *txOrder) Set(str string) error { switch str { case "asc", "ascending", "earliest": *o = "asc" - case "desc", "descending", "latest": + case "des", "desc", "descending", "latest": *o = "desc" default: return fmt.Errorf(`must be "asc" or "desc"`) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 21b504b..cee03d0 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -273,6 +273,8 @@ CLI Completion "Token ID of a FAT chain") flags.VarPF(paramsToken.IssuerChainID, "identity", "I", "Issuer Identity Chain ID of a FAT chain").DefValue = "" + flags.BoolVarP(¶msToken.IncludePending, "includepending", "P", false, + "Include pending transactions") generateCmplFlags(cmd, rootCmplCmd.Flags) return cmd @@ -288,6 +290,8 @@ var apiCmplFlags = complete.Flags{ var tokenCmplFlags = complete.Flags{ "--chainid": PredictChainIDs, "-C": PredictChainIDs, + "--pending": complete.PredictNothing, + "-P": complete.PredictNothing, } func validateRunCompletionFlags(cmd *cobra.Command, _ []string) error { diff --git a/db/apply.go b/db/apply.go index 628f4d7..24a2758 100644 --- a/db/apply.go +++ b/db/apply.go @@ -156,12 +156,12 @@ func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { return nil, nil } -func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, +func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, txErr, err error) { tx = fat0.NewTransaction(e) - defer chain.Save(&tx)(&txErr, &err) + defer chain.Save(tx)(&txErr, &err) - if txErr, err = chain.applyTx(ei, &tx); err != nil || txErr != nil { + if txErr, err = chain.applyTx(ei, tx); err != nil || txErr != nil { return } @@ -205,12 +205,12 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, return } -func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx fat1.Transaction, +func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, txErr, err error) { tx = fat1.NewTransaction(e) - defer chain.Save(&tx)(&txErr, &err) + defer chain.Save(tx)(&txErr, &err) - if txErr, err = chain.applyTx(ei, &tx); err != nil || txErr != nil { + if txErr, err = chain.applyTx(ei, tx); err != nil || txErr != nil { return } diff --git a/db/chain.go b/db/chain.go index 4b2e97a..be64f7c 100644 --- a/db/chain.go +++ b/db/chain.go @@ -27,6 +27,7 @@ import ( "io/ioutil" "os" "strings" + "time" //"strings" "crawshaw.io/sqlite" @@ -254,3 +255,9 @@ func (chain *Chain) Get() (*sqlite.Conn, func()) { conn := chain.Pool.Get(nil) return conn, func() { chain.Pool.Put(conn) } } + +func (chain *Chain) LatestEntryTimestamp() time.Time { + entries := chain.Head.Entries + lastID := len(entries) - 1 + return entries[lastID].Timestamp +} diff --git a/engine/chain.go b/engine/chain.go index def441d..0d2ad4c 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -96,7 +96,6 @@ func (p *Pending) Sync(chain db.Chain) error { if err := p.Chain.Conn.ChangesetApply(inverse, nil, conflictFn); err != nil { return err } - chain.Log.Debug("apply PendSess") } if p.MainSess != nil { @@ -108,7 +107,6 @@ func (p *Pending) Sync(chain db.Chain) error { if err := p.Chain.Conn.ChangesetApply(changeset, nil, nil); err != nil { return err } - chain.Log.Debug("apply MainSess") } return nil @@ -262,6 +260,8 @@ func (p *Pending) Open(chain db.Chain) (err error) { } c.Close() // Redundant connection... p.Chain = chain + log := p.Chain.Log.Entry + p.Chain.Log.Entry = log.WithField("db", "pending") p.Chain.Conn, p.Chain.Pool = conn, pool return } diff --git a/engine/engine.go b/engine/engine.go index 875f43e..dd2d490 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -42,7 +42,7 @@ var ( ) const ( - scanInterval = 15 * time.Second + scanInterval = 30 * time.Second ) // Start launches the main engine goroutine, which loads state and starts the diff --git a/engine/process.go b/engine/process.go index 9ae10e6..58e41b2 100644 --- a/engine/process.go +++ b/engine/process.go @@ -72,7 +72,6 @@ func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { cached.Timestamp = e.Timestamp *e = cached } - chain.Log.Debug("main sync") if err := chain.Pending.Sync(chain.Chain); err != nil { return err } diff --git a/fat/entry.go b/fat/entry.go index a21fb0f..2578a64 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -41,6 +41,7 @@ type Transaction interface { Validate(factom.IDKey) error IsCoinbase() bool FactomEntry() factom.Entry + UnmarshalEntry() error } // Entry has variables and methods common to all fat0 entries. diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index c2bbd8c..3d48db2 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -43,8 +43,8 @@ type Transaction struct { var _ fat.Transaction = &Transaction{} // NewTransaction returns a Transaction initialized with the given entry. -func NewTransaction(entry factom.Entry) Transaction { - return Transaction{Entry: fat.Entry{Entry: entry}} +func NewTransaction(entry factom.Entry) *Transaction { + return &Transaction{Entry: fat.Entry{Entry: entry}} } func (t *Transaction) UnmarshalJSON(data []byte) error { diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index 0737b2b..d4a4a08 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -44,8 +44,8 @@ type Transaction struct { var _ fat.Transaction = &Transaction{} // NewTransaction returns a Transaction initialized with the given entry. -func NewTransaction(entry factom.Entry) Transaction { - return Transaction{Entry: fat.Entry{Entry: entry}} +func NewTransaction(entry factom.Entry) *Transaction { + return &Transaction{Entry: fat.Entry{Entry: entry}} } func (t *Transaction) UnmarshalJSON(data []byte) error { diff --git a/srv/methods.go b/srv/methods.go index 91fbd35..c788c53 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -97,6 +97,7 @@ type ResultGetTransaction struct { Hash *factom.Bytes32 `json:"entryhash"` Timestamp int64 `json:"timestamp"` Tx interface{} `json:"data"` + Pending bool `json:"pending,omitempty"` } func getTransaction(getEntry bool) jrpc.MethodFunc { @@ -120,30 +121,26 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { return entry } + result := ResultGetTransaction{ + Hash: entry.Hash, + Timestamp: entry.Timestamp.Unix(), + Pending: chain.LatestEntryTimestamp().Before(entry.Timestamp), + } + + var tx fat.Transaction switch chain.Type { case fat0.Type: - tx := fat0.NewTransaction(entry) - if err := tx.UnmarshalEntry(); err != nil { - panic(err) - } - return ResultGetTransaction{ - Hash: tx.Hash, - Timestamp: tx.Timestamp.Unix(), - Tx: tx, - } + tx = fat0.NewTransaction(entry) case fat1.Type: - tx := fat1.NewTransaction(entry) - if err := tx.UnmarshalEntry(); err != nil { - panic(err) - } - return ResultGetTransaction{ - Hash: tx.Hash, - Timestamp: tx.Timestamp.Unix(), - Tx: tx, - } + tx = fat1.NewTransaction(entry) default: panic(fmt.Sprintf("unknown FAT type: %v", chain.Type)) } + if err := tx.UnmarshalEntry(); err != nil { + panic(err) + } + result.Tx = tx + return result } } @@ -186,34 +183,27 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { return entries } - switch chain.Type { - case fat0.Type: - txs := make([]ResultGetTransaction, len(entries)) - for i := range txs { - tx := fat0.NewTransaction(entries[i]) - if err := tx.UnmarshalEntry(); err != nil { - panic(err) - } - txs[i].Hash = entries[i].Hash - txs[i].Timestamp = entries[i].Timestamp.Unix() - txs[i].Tx = tx + txs := make([]ResultGetTransaction, len(entries)) + for i := range txs { + entry := entries[i] + var tx fat.Transaction + switch chain.Type { + case fat0.Type: + tx = fat0.NewTransaction(entry) + case fat1.Type: + tx = fat1.NewTransaction(entry) + default: + panic(fmt.Sprintf("unknown FAT type: %v", chain.Type)) } - return txs - case fat1.Type: - txs := make([]ResultGetTransaction, len(entries)) - for i := range txs { - tx := fat1.NewTransaction(entries[i]) - if err := tx.UnmarshalEntry(); err != nil { - panic(err) - } - txs[i].Hash = entries[i].Hash - txs[i].Timestamp = entries[i].Timestamp.Unix() - txs[i].Tx = tx + if err := tx.UnmarshalEntry(); err != nil { + panic(err) } - return txs - default: - panic(fmt.Sprintf("unknown FAT type: %v", chain.Type)) + txs[i].Hash = entry.Hash + txs[i].Timestamp = entry.Timestamp.Unix() + txs[i].Pending = chain.LatestEntryTimestamp().Before(entry.Timestamp) + txs[i].Tx = tx } + return txs } } From dbe906281e096866cccaf1ccb70a21569e2aaa7f Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 10 Sep 2019 16:05:46 -0800 Subject: [PATCH 044/124] fix(srv): Return rolled up NFTokens for get-nf-balance --- db/nftoken.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/db/nftoken.go b/db/nftoken.go index 6cac61c..e7e865f 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -143,7 +143,7 @@ func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint64) ([]fat1 } func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, - page, limit uint64, order string) ([]fat1.NFTokenID, error) { + page, limit uint64, order string) (fat1.NFTokens, error) { if page == 0 { return nil, fmt.Errorf("invalid page") } @@ -158,7 +158,7 @@ func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, stmt := sql.Prep(conn) defer stmt.Reset() - var nfTkns []fat1.NFTokenID + nfTkns := make(fat1.NFTokens) for { hasRow, err := stmt.Step() if err != nil { @@ -167,7 +167,9 @@ func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, if !hasRow { break } - nfTkns = append(nfTkns, fat1.NFTokenID(stmt.ColumnInt64(0))) + if err := nfTkns.Set(fat1.NFTokenID(stmt.ColumnInt64(0))); err != nil { + panic(err) + } } return nfTkns, nil } From 975097bbbcff1c938bf1579c51c1df3f7eb373b5 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 10 Sep 2019 16:22:31 -0800 Subject: [PATCH 045/124] fix(engine): Manage pending db connection cleanup better --- engine/chain.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/engine/chain.go b/engine/chain.go index 0d2ad4c..78dd5d0 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -246,19 +246,19 @@ func (chain *Chain) Apply(c *factom.Client, func (p *Pending) Open(chain db.Chain) (err error) { dbURI := fmt.Sprintf("file:%v?mode=memory&cache=shared", chain.ID) - conn, err := chain.Conn.BackupToDB("", dbURI) - defer func() { - if err != nil { - conn.Close() - } - }() + c, err := chain.Conn.BackupToDB("", dbURI) + if err != nil { + return + } + // Close connection to in memory database only after we have the other + // connections open. + defer c.Close() // Open the pending database connection and pool - c, pool, err := db.OpenConnPool(dbURI) + conn, pool, err := db.OpenConnPool(dbURI) if err != nil { return } - c.Close() // Redundant connection... p.Chain = chain log := p.Chain.Log.Entry p.Chain.Log.Entry = log.WithField("db", "pending") From 3db5eae0ac757deee49bf2212cfd7a99e635c20d Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 10 Sep 2019 18:58:09 -0800 Subject: [PATCH 046/124] build(go.mod): Update some dependencies golang.org/x/crypto, github.com/stretchr/testify --- go.mod | 6 ++---- go.sum | 7 +++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index f84da4d..d17a5ab 100644 --- a/go.mod +++ b/go.mod @@ -22,14 +22,12 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.4.0 - github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + github.com/stretchr/testify v1.4.0 + golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect golang.org/x/text v0.3.2 // indirect ) -replace github.com/gocraft/dbr => github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea - replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455 diff --git a/go.sum b/go.sum index 7626e36..1a1965d 100644 --- a/go.sum +++ b/go.sum @@ -138,6 +138,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -145,6 +146,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -158,8 +161,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 h1:mgAKeshyNqWKdENOnQsg+8dRTwZFIwFaO3HNl52sweA= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From 5f35d8834c9ed91818e320eb4aaf196af4b02456 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 10 Sep 2019 19:05:30 -0800 Subject: [PATCH 047/124] build(factom): Move factom to its own go module in external repo --- Makefile | 6 - cli/cmd/getbalance.go | 2 +- cli/cmd/getchains.go | 2 +- cli/cmd/gettransactions.go | 2 +- cli/cmd/issue.go | 2 +- cli/cmd/predict.go | 2 +- cli/cmd/root.go | 2 +- cli/cmd/transact.go | 2 +- cli/cmd/transactfat0.go | 2 +- cli/cmd/transactfat1.go | 2 +- db/address.go | 2 +- db/apply.go | 2 +- db/chain.go | 2 +- db/eblock.go | 2 +- db/entry.go | 2 +- db/gentestdb.go | 2 +- db/metadata.go | 2 +- db/nftoken.go | 2 +- db/validate.go | 2 +- engine/chain.go | 2 +- engine/chainmap.go | 2 +- engine/engine.go | 2 +- engine/process.go | 2 +- factom/address.go | 777 -------------------------- factom/address_test.go | 382 ------------- factom/bytes.go | 161 ------ factom/bytes_test.go | 175 ------ factom/client.go | 76 --- factom/dblock.go | 351 ------------ factom/dblock_eblock_entry_test.go | 246 -------- factom/doc.go | 60 -- factom/eblock.go | 479 ---------------- factom/entry.go | 460 --------------- factom/entry_test.go | 228 -------- factom/gen.go | 27 - factom/genmain.go | 92 --- factom/heights.go | 54 -- factom/heights_test.go | 42 -- factom/identity.go | 129 ----- factom/identity_test.go | 242 -------- factom/idkey.go | 70 --- factom/idkey.tmpl | 250 --------- factom/idkey_gen.go | 834 ---------------------------- factom/idkey_gen_test.go | 488 ---------------- factom/idkey_test.tmpl | 225 -------- factom/networkid.go | 71 --- factom/payload.go | 72 --- factom/pendingentries.go | 90 --- factom/pendingentries_test.go | 90 --- factom/rcdprivatekey.go | 39 -- factom/varintf/varintf.go | 71 --- factom/varintf/varintf_test.go | 117 ---- fat/chainid.go | 2 +- fat/entry.go | 2 +- fat/entry_test.go | 2 +- fat/fat0/addressamountmap.go | 2 +- fat/fat0/transaction.go | 2 +- fat/fat0/transaction_test.go | 2 +- fat/fat1/addressnftokensmap.go | 2 +- fat/fat1/addressnftokensmap_test.go | 2 +- fat/fat1/transaction.go | 2 +- fat/fat1/transaction_test.go | 2 +- fat/issuance.go | 2 +- fat/issuance_test.go | 2 +- flag/flag.go | 2 +- flag/list.go | 2 +- flag/predict.go | 2 +- go.mod | 1 + go.sum | 2 + srv/methods.go | 2 +- srv/methods_test.go | 2 +- srv/params.go | 2 +- 72 files changed, 43 insertions(+), 6444 deletions(-) delete mode 100644 factom/address.go delete mode 100644 factom/address_test.go delete mode 100644 factom/bytes.go delete mode 100644 factom/bytes_test.go delete mode 100644 factom/client.go delete mode 100644 factom/dblock.go delete mode 100644 factom/dblock_eblock_entry_test.go delete mode 100644 factom/doc.go delete mode 100644 factom/eblock.go delete mode 100644 factom/entry.go delete mode 100644 factom/entry_test.go delete mode 100644 factom/gen.go delete mode 100644 factom/genmain.go delete mode 100644 factom/heights.go delete mode 100644 factom/heights_test.go delete mode 100644 factom/identity.go delete mode 100644 factom/identity_test.go delete mode 100644 factom/idkey.go delete mode 100644 factom/idkey.tmpl delete mode 100644 factom/idkey_gen.go delete mode 100644 factom/idkey_gen_test.go delete mode 100644 factom/idkey_test.tmpl delete mode 100644 factom/networkid.go delete mode 100644 factom/payload.go delete mode 100644 factom/pendingentries.go delete mode 100644 factom/pendingentries_test.go delete mode 100644 factom/rcdprivatekey.go delete mode 100644 factom/varintf/varintf.go delete mode 100644 factom/varintf/varintf_test.go diff --git a/Makefile b/Makefile index 8e4bbdd..ad014e1 100644 --- a/Makefile +++ b/Makefile @@ -41,8 +41,6 @@ CLI_LDFLAGS = "$(GO_LDFLAGS)/cli/cmd.Revision=$(REVISION)" DEPSRC = go.mod go.sum SRC = $(DEPSRC) $(filter-out %_test.go,$(wildcard *.go */*.go */*/*.go)) -GENSRC=factom/idkey_gen.go factom/idkey_gen_test.go - FATDSRC=$(filter-out cli/%,$(SRC)) $(GENSRC) fatd: $(FATDSRC) go build -ldflags=$(FATD_LDFLAGS) ./ @@ -77,10 +75,6 @@ fat-cli.exe: $(CLISRC) fat-cli-linux: $(CLISRC) env GOOS=linux GOARCH=amd64 go build -ldflags=$(CLI_LDFLAGS) -o fat-cli ./cli -$(GENSRC): factom/gen.go factom/genmain.go $(wildcard factom/*.tmpl) - go generate ./factom - - .PHONY: clean clean-gen purge-db unpurge-db clean: diff --git a/cli/cmd/getbalance.go b/cli/cmd/getbalance.go index 507a9b1..f8ded52 100644 --- a/cli/cmd/getbalance.go +++ b/cli/cmd/getbalance.go @@ -26,7 +26,7 @@ import ( "fmt" "math" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/Factom-Asset-Tokens/fatd/srv" diff --git a/cli/cmd/getchains.go b/cli/cmd/getchains.go index 9da8e1d..322fa87 100644 --- a/cli/cmd/getchains.go +++ b/cli/cmd/getchains.go @@ -25,7 +25,7 @@ package cmd import ( "fmt" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/srv" "github.com/posener/complete" "github.com/spf13/cobra" diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index 7fd71ac..7eca104 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -28,7 +28,7 @@ import ( "strings" "time" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/Factom-Asset-Tokens/fatd/srv" diff --git a/cli/cmd/issue.go b/cli/cmd/issue.go index 904b77a..02e86e1 100644 --- a/cli/cmd/issue.go +++ b/cli/cmd/issue.go @@ -27,7 +27,7 @@ import ( "fmt" "strings" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/srv" diff --git a/cli/cmd/predict.go b/cli/cmd/predict.go index a08d2c0..59c8b66 100644 --- a/cli/cmd/predict.go +++ b/cli/cmd/predict.go @@ -28,7 +28,7 @@ import ( "strings" "time" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/srv" "github.com/posener/complete" ) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index cee03d0..de8394b 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -31,7 +31,7 @@ import ( "time" jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/srv" homedir "github.com/mitchellh/go-homedir" diff --git a/cli/cmd/transact.go b/cli/cmd/transact.go index 23e11ba..8e44561 100644 --- a/cli/cmd/transact.go +++ b/cli/cmd/transact.go @@ -27,7 +27,7 @@ import ( "fmt" "math" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" diff --git a/cli/cmd/transactfat0.go b/cli/cmd/transactfat0.go index 49a7fcb..ac3d188 100644 --- a/cli/cmd/transactfat0.go +++ b/cli/cmd/transactfat0.go @@ -27,7 +27,7 @@ import ( "strconv" "strings" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" diff --git a/cli/cmd/transactfat1.go b/cli/cmd/transactfat1.go index 7ab1e74..09ee4c5 100644 --- a/cli/cmd/transactfat1.go +++ b/cli/cmd/transactfat1.go @@ -26,7 +26,7 @@ import ( "fmt" "strings" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" diff --git a/db/address.go b/db/address.go index 8c2c635..7761424 100644 --- a/db/address.go +++ b/db/address.go @@ -5,7 +5,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" ) func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) { diff --git a/db/apply.go b/db/apply.go index 24a2758..7340fb4 100644 --- a/db/apply.go +++ b/db/apply.go @@ -4,7 +4,7 @@ import ( "fmt" "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" diff --git a/db/chain.go b/db/chain.go index be64f7c..0d02334 100644 --- a/db/chain.go +++ b/db/chain.go @@ -33,7 +33,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" _log "github.com/Factom-Asset-Tokens/fatd/log" ) diff --git a/db/eblock.go b/db/eblock.go index ec692ba..548e021 100644 --- a/db/eblock.go +++ b/db/eblock.go @@ -5,7 +5,7 @@ import ( "time" "crawshaw.io/sqlite" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" ) func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) error { diff --git a/db/entry.go b/db/entry.go index 6366e74..33b5677 100644 --- a/db/entry.go +++ b/db/entry.go @@ -7,7 +7,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) diff --git a/db/gentestdb.go b/db/gentestdb.go index 00934ec..e8d2c3b 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -9,7 +9,7 @@ import ( "os" "github.com/Factom-Asset-Tokens/fatd/db" - . "github.com/Factom-Asset-Tokens/fatd/factom" + . "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" fflag "github.com/Factom-Asset-Tokens/fatd/flag" ) diff --git a/db/metadata.go b/db/metadata.go index 23dce0a..01d73d9 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -4,7 +4,7 @@ import ( "fmt" "crawshaw.io/sqlite" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" ) diff --git a/db/nftoken.go b/db/nftoken.go index e7e865f..0cb1870 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -6,7 +6,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) diff --git a/db/validate.go b/db/validate.go index 400370d..6c9d234 100644 --- a/db/validate.go +++ b/db/validate.go @@ -7,7 +7,7 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/AdamSLevy/sqlitechangeset" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" ) diff --git a/engine/chain.go b/engine/chain.go index 78dd5d0..8eb300a 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -31,7 +31,7 @@ import ( jrpc "github.com/AdamSLevy/jsonrpc2/v11" "github.com/Factom-Asset-Tokens/fatd/db" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" ) diff --git a/engine/chainmap.go b/engine/chainmap.go index ddfcc08..8348731 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -28,7 +28,7 @@ import ( "sync" "github.com/Factom-Asset-Tokens/fatd/db" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" ) diff --git a/engine/engine.go b/engine/engine.go index dd2d490..1bb9995 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -30,7 +30,7 @@ import ( "github.com/nightlyone/lockfile" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" _log "github.com/Factom-Asset-Tokens/fatd/log" ) diff --git a/engine/process.go b/engine/process.go index 58e41b2..39ad058 100644 --- a/engine/process.go +++ b/engine/process.go @@ -26,7 +26,7 @@ import ( "time" jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" ) diff --git a/factom/address.go b/factom/address.go deleted file mode 100644 index 881ae94..0000000 --- a/factom/address.go +++ /dev/null @@ -1,777 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "crypto/rand" - "crypto/sha256" - "database/sql" - "database/sql/driver" - "fmt" - - "golang.org/x/crypto/ed25519" -) - -// Notes: This file contains all types, interfaces, and methods related to -// Factom Addresses as specified by -// https://github.com/FactomProject/FactomDocs/blob/master/factomDataStructureDetails.md -// -// There are four Factom address types, forming two pairs: public and private -// Factoid addresses, and public and private Entry Credit addresses. All -// addresses are a 32 byte payload encoded using base58check with various -// prefixes. - -// Address is the interface implemented by the four address types: FAAddress, -// FsAddress, ECAddress, and EsAddress. -type Address interface { - // PrefixBytes returns the prefix bytes for the Address. - PrefixBytes() []byte - // PrefixString returns the encoded prefix string for the Address. - PrefixString() string - - // String encodes the address to a base58check string with the - // appropriate prefix. - String() string - // Payload returns the address as a byte array. - Payload() [sha256.Size]byte - - // PublicAddress returns the corresponding public address in an Address - // interface. Public addresses return themselves. Private addresses - // compute the public address. - PublicAddress() Address - // GetPrivateAddress returns the corresponding private address in a - // PrivateAddress interface. Public addresses query factom-walletd for - // the private address. Private addresses return themselves. - GetPrivateAddress(*Client) (PrivateAddress, error) - - // GetBalance returns the current balance for the address. - GetBalance(*Client) (uint64, error) - - // Remove queries factom-walletd to remove the public and private - // addresses from its database. - // WARNING: DESTRUCTIVE ACTION! LOSS OF KEYS AND FUNDS MAY RESULT! - Remove(*Client) error -} - -// PrivateAddress is the interface implemented by the two private address -// types: FsAddress, and EsAddress. -type PrivateAddress interface { - Address - - // PrivateKey returns the ed25519.PrivateKey which can be used for - // signing data. - PrivateKey() ed25519.PrivateKey - // PublicKey returns the ed25519.PublicKey which can be used for - // verifying signatures. - PublicKey() ed25519.PublicKey -} - -// FAAddress is a Public Factoid Address. -type FAAddress [sha256.Size]byte - -// FsAddress is the secret key to a FAAddress. -type FsAddress [sha256.Size]byte - -// ECAddress is a Public Entry Credit Address. -type ECAddress [sha256.Size]byte - -// EsAddress is the secret key to a ECAddress. -type EsAddress [sha256.Size]byte - -// Ensure that the Address and PrivateAddress interfaces are implemented. -var _ Address = FAAddress{} -var _ PrivateAddress = FsAddress{} -var _ Address = ECAddress{} -var _ PrivateAddress = EsAddress{} - -// Payload returns adr as a byte array. -func (adr FAAddress) Payload() [sha256.Size]byte { - return adr -} - -// Payload returns adr as a byte array. -func (adr FsAddress) Payload() [sha256.Size]byte { - return adr -} - -// Payload returns adr as a byte array. -func (adr ECAddress) Payload() [sha256.Size]byte { - return adr -} - -// Payload returns adr as a byte array. -func (adr EsAddress) Payload() [sha256.Size]byte { - return adr -} - -// payload returns adr as payload. This is syntactic sugar useful in other -// methods that leverage payload. -func (adr FAAddress) payload() payload { - return payload(adr) -} -func (adr FsAddress) payload() payload { - return payload(adr) -} -func (adr ECAddress) payload() payload { - return payload(adr) -} -func (adr EsAddress) payload() payload { - return payload(adr) -} - -// payloadPtr returns adr as *payload. This is syntactic sugar useful in other -// methods that leverage *payload. -func (adr *FAAddress) payloadPtr() *payload { - return (*payload)(adr) -} -func (adr *FsAddress) payloadPtr() *payload { - return (*payload)(adr) -} -func (adr *ECAddress) payloadPtr() *payload { - return (*payload)(adr) -} -func (adr *EsAddress) payloadPtr() *payload { - return (*payload)(adr) -} - -var ( - faPrefixBytes = [...]byte{0x5f, 0xb1} - fsPrefixBytes = [...]byte{0x64, 0x78} - ecPrefixBytes = [...]byte{0x59, 0x2a} - esPrefixBytes = [...]byte{0x5d, 0xb6} -) - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x5f, 0xb1}. -func (FAAddress) PrefixBytes() []byte { - prefix := faPrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x64, 0x78}. -func (FsAddress) PrefixBytes() []byte { - prefix := fsPrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x59, 0x2a}. -func (ECAddress) PrefixBytes() []byte { - prefix := ecPrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x5d, 0xb6}. -func (EsAddress) PrefixBytes() []byte { - prefix := esPrefixBytes - return prefix[:] -} - -const ( - faPrefixStr = "FA" - fsPrefixStr = "Fs" - ecPrefixStr = "EC" - esPrefixStr = "Es" -) - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "FA". -func (FAAddress) PrefixString() string { - return faPrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "Fs". -func (FsAddress) PrefixString() string { - return fsPrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "EC". -func (ECAddress) PrefixString() string { - return ecPrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "Es". -func (EsAddress) PrefixString() string { - return esPrefixStr -} - -// String encodes adr into its human readable form: a base58check string with -// adr.PrefixBytes(). -func (adr FAAddress) String() string { - return adr.payload().StringPrefix(adr.PrefixBytes()) -} - -// String encodes adr into its human readable form: a base58check string with -// adr.PrefixBytes(). -func (adr FsAddress) String() string { - return adr.payload().StringPrefix(adr.PrefixBytes()) -} - -// String encodes adr into its human readable form: a base58check string with -// adr.PrefixBytes(). -func (adr ECAddress) String() string { - return adr.payload().StringPrefix(adr.PrefixBytes()) -} - -// String encodes adr into its human readable form: a base58check string with -// adr.PrefixBytes(). -func (adr EsAddress) String() string { - return adr.payload().StringPrefix(adr.PrefixBytes()) -} - -// MarshalJSON encodes adr as a JSON string using adr.String(). -func (adr FAAddress) MarshalJSON() ([]byte, error) { - return adr.payload().MarshalJSONPrefix(adr.PrefixBytes()) -} - -// MarshalJSON encodes adr as a JSON string using adr.String(). -func (adr FsAddress) MarshalJSON() ([]byte, error) { - return adr.payload().MarshalJSONPrefix(adr.PrefixBytes()) -} - -// MarshalJSON encodes adr as a JSON string using adr.String(). -func (adr ECAddress) MarshalJSON() ([]byte, error) { - return adr.payload().MarshalJSONPrefix(adr.PrefixBytes()) -} - -// MarshalJSON encodes adr as a JSON string using adr.String(). -func (adr EsAddress) MarshalJSON() ([]byte, error) { - return adr.payload().MarshalJSONPrefix(adr.PrefixBytes()) -} - -const adrStrLen = 52 - -// NewAddress parses adrStr and returns the correct address type as an Address -// interface. This is useful when the address type isn't known prior to parsing -// adrStr. If the address type is known ahead of time, it is generally better -// to just use the appropriate concrete type. -func NewAddress(adrStr string) (Address, error) { - if len(adrStr) != adrStrLen { - return nil, fmt.Errorf("invalid length") - } - switch adrStr[:2] { - case FAAddress{}.PrefixString(): - return NewFAAddress(adrStr) - case FsAddress{}.PrefixString(): - return NewFsAddress(adrStr) - case ECAddress{}.PrefixString(): - return NewECAddress(adrStr) - case EsAddress{}.PrefixString(): - return NewEsAddress(adrStr) - default: - return nil, fmt.Errorf("unrecognized prefix") - } -} - -// NewPublicAddress parses adrStr and returns the correct address type as an -// Address interface. If adrStr is not a public address then an "invalid -// prefix" error is returned. This is useful when the address type isn't known -// prior to parsing adrStr, but must be a public address. If the address type -// is known ahead of time, it is generally better to just use the appropriate -// concrete type. -func NewPublicAddress(adrStr string) (Address, error) { - if len(adrStr) != adrStrLen { - return nil, fmt.Errorf("invalid length") - } - switch adrStr[:2] { - case FAAddress{}.PrefixString(): - return NewFAAddress(adrStr) - case ECAddress{}.PrefixString(): - return NewECAddress(adrStr) - case FsAddress{}.PrefixString(): - fallthrough - case EsAddress{}.PrefixString(): - return nil, fmt.Errorf("invalid prefix") - default: - return nil, fmt.Errorf("unrecognized prefix") - } -} - -// NewPrivateAddress parses adrStr and returns the correct address type as a -// PrivateAddress interface. If adrStr is not a private address then an -// "invalid prefix" error is returned. This is useful when the address type -// isn't known prior to parsing adrStr, but must be a private address. If the -// address type is known ahead of time, it is generally better to just use the -// appropriate concrete type. -func NewPrivateAddress(adrStr string) (PrivateAddress, error) { - if len(adrStr) != adrStrLen { - return nil, fmt.Errorf("invalid length") - } - switch adrStr[:2] { - case FsAddress{}.PrefixString(): - return NewFsAddress(adrStr) - case EsAddress{}.PrefixString(): - return NewEsAddress(adrStr) - case FAAddress{}.PrefixString(): - fallthrough - case ECAddress{}.PrefixString(): - return nil, fmt.Errorf("invalid prefix") - default: - return nil, fmt.Errorf("unrecognized prefix") - } -} - -// GenerateFsAddress generates a secure random private Factoid address using -// crypto/rand.Random as the source of randomness. -func GenerateFsAddress() (FsAddress, error) { - return generatePrivKey() -} - -// GenerateEsAddress generates a secure random private Entry Credit address -// using crypto/rand.Random as the source of randomness. -func GenerateEsAddress() (EsAddress, error) { - return generatePrivKey() -} -func generatePrivKey() (key [sha256.Size]byte, err error) { - var priv ed25519.PrivateKey - if _, priv, err = ed25519.GenerateKey(rand.Reader); err != nil { - return - } - copy(key[:], priv) - return key, nil -} - -// NewFAAddress attempts to parse adrStr into a new FAAddress. -func NewFAAddress(adrStr string) (adr FAAddress, err error) { - err = adr.Set(adrStr) - return -} - -// NewFsAddress attempts to parse adrStr into a new FsAddress. -func NewFsAddress(adrStr string) (adr FsAddress, err error) { - err = adr.Set(adrStr) - return -} - -// NewECAddress attempts to parse adrStr into a new ECAddress. -func NewECAddress(adrStr string) (adr ECAddress, err error) { - err = adr.Set(adrStr) - return -} - -// NewEsAddress attempts to parse adrStr into a new EsAddress. -func NewEsAddress(adrStr string) (adr EsAddress, err error) { - err = adr.Set(adrStr) - return -} - -// Set attempts to parse adrStr into adr. -func (adr *FAAddress) Set(adrStr string) error { - return adr.payloadPtr().SetPrefix(adrStr, adr.PrefixString()) -} - -// Set attempts to parse adrStr into adr. -func (adr *FsAddress) Set(adrStr string) error { - return adr.payloadPtr().SetPrefix(adrStr, adr.PrefixString()) -} - -// Set attempts to parse adrStr into adr. -func (adr *ECAddress) Set(adrStr string) error { - return adr.payloadPtr().SetPrefix(adrStr, adr.PrefixString()) -} - -// Set attempts to parse adrStr into adr. -func (adr *EsAddress) Set(adrStr string) error { - return adr.payloadPtr().SetPrefix(adrStr, adr.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable public Factoid -// address into adr. -func (adr *FAAddress) UnmarshalJSON(data []byte) error { - return adr.payloadPtr().UnmarshalJSONPrefix(data, adr.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable secret Factoid -// address into adr. -func (adr *FsAddress) UnmarshalJSON(data []byte) error { - return adr.payloadPtr().UnmarshalJSONPrefix(data, adr.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable public Entry -// Credit address into adr. -func (adr *ECAddress) UnmarshalJSON(data []byte) error { - return adr.payloadPtr().UnmarshalJSONPrefix(data, adr.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable secret Entry -// Credit address into adr. -func (adr *EsAddress) UnmarshalJSON(data []byte) error { - return adr.payloadPtr().UnmarshalJSONPrefix(data, adr.PrefixString()) -} - -// GetPrivateAddress queries factom-walletd for the secret address -// corresponding to adr and returns it as a PrivateAddress. -func (adr FAAddress) GetPrivateAddress(c *Client) (PrivateAddress, error) { - return adr.GetFsAddress(c) -} - -// GetPrivateAddress returns adr as a PrivateAddress. -func (adr FsAddress) GetPrivateAddress(_ *Client) (PrivateAddress, error) { - return adr, nil -} - -// GetPrivateAddress queries factom-walletd for the secret address -// corresponding to adr and returns it as a PrivateAddress. -func (adr ECAddress) GetPrivateAddress(c *Client) (PrivateAddress, error) { - return adr.GetEsAddress(c) -} - -// GetPrivateAddress returns adr as a PrivateAddress. -func (adr EsAddress) GetPrivateAddress(_ *Client) (PrivateAddress, error) { - return adr, nil -} - -// GetFsAddress queries factom-walletd for the FsAddress corresponding to adr. -func (adr FAAddress) GetFsAddress(c *Client) (FsAddress, error) { - var privAdr FsAddress - err := c.GetAddress(adr, &privAdr) - return privAdr, err -} - -// GetEsAddress queries factom-walletd for the EsAddress corresponding to adr. -func (adr ECAddress) GetEsAddress(c *Client) (EsAddress, error) { - var privAdr EsAddress - err := c.GetAddress(adr, &privAdr) - return privAdr, err -} - -type walletAddress struct{ Address Address } - -// GetAddress queries factom-walletd for the privAdr corresponding to pubAdr. -// If the returned error is nil, then privAdr is now populated. Note that -// privAdr must be a pointer to a concrete type implementing PrivateAddress. -func (c *Client) GetAddress(pubAdr Address, privAdr PrivateAddress) error { - params := walletAddress{Address: pubAdr} - result := struct{ Secret PrivateAddress }{Secret: privAdr} - if err := c.WalletdRequest("address", params, &result); err != nil { - return err - } - return nil -} - -type walletAddressPublic struct{ Public string } -type walletAddressSecret struct{ Secret string } -type walletAddressesPublic struct{ Addresses []walletAddressPublic } -type walletAddressesSecret struct{ Addresses []walletAddressSecret } - -// GetAddresses queries factom-walletd for all public addresses. -func (c *Client) GetAddresses() ([]Address, error) { - var result walletAddressesPublic - if err := c.WalletdRequest("all-addresses", nil, &result); err != nil { - return nil, err - } - addresses := make([]Address, 0, len(result.Addresses)) - for _, adrStr := range result.Addresses { - adr, err := NewAddress(adrStr.Public) - if err != nil { - return nil, err - } - addresses = append(addresses, adr) - } - return addresses, nil -} - -// GetPrivateAddresses queries factom-walletd for all private addresses. -func (c *Client) GetPrivateAddresses() ([]PrivateAddress, error) { - var result walletAddressesSecret - if err := c.WalletdRequest("all-addresses", nil, &result); err != nil { - return nil, err - } - addresses := make([]PrivateAddress, 0, len(result.Addresses)) - for _, adrStr := range result.Addresses { - adr, err := NewPrivateAddress(adrStr.Secret) - if err != nil { - return nil, err - } - addresses = append(addresses, adr) - } - return addresses, nil -} - -// GetFAAddresses queries factom-walletd for all public Factoid addresses. -func (c *Client) GetFAAddresses() ([]FAAddress, error) { - var result walletAddressesPublic - if err := c.WalletdRequest("all-addresses", nil, &result); err != nil { - return nil, err - } - addresses := make([]FAAddress, 0, len(result.Addresses)) - for _, adrStr := range result.Addresses { - adr, err := NewFAAddress(adrStr.Public) - if err != nil { - continue - } - addresses = append(addresses, adr) - } - return addresses, nil -} - -// GetFsAddresses queries factom-walletd for all secret Factoid addresses. -func (c *Client) GetFsAddresses() ([]FsAddress, error) { - var result walletAddressesSecret - if err := c.WalletdRequest("all-addresses", nil, &result); err != nil { - return nil, err - } - addresses := make([]FsAddress, 0, len(result.Addresses)) - for _, adrStr := range result.Addresses { - adr, err := NewFsAddress(adrStr.Secret) - if err != nil { - continue - } - addresses = append(addresses, adr) - } - return addresses, nil -} - -// GetECAddresses queries factom-walletd for all public Entry Credit addresses. -func (c *Client) GetECAddresses() ([]ECAddress, error) { - var result walletAddressesPublic - if err := c.WalletdRequest("all-addresses", nil, &result); err != nil { - return nil, err - } - addresses := make([]ECAddress, 0, len(result.Addresses)) - for _, adrStr := range result.Addresses { - adr, err := NewECAddress(adrStr.Public) - if err != nil { - continue - } - addresses = append(addresses, adr) - } - return addresses, nil -} - -// GetEsAddresses queries factom-walletd for all secret Entry Credit addresses. -func (c *Client) GetEsAddresses() ([]EsAddress, error) { - var result walletAddressesSecret - if err := c.WalletdRequest("all-addresses", nil, &result); err != nil { - return nil, err - } - addresses := make([]EsAddress, 0, len(result.Addresses)) - for _, adrStr := range result.Addresses { - adr, err := NewEsAddress(adrStr.Secret) - if err != nil { - continue - } - addresses = append(addresses, adr) - } - return addresses, nil -} - -// Save adr with factom-walletd. -func (adr FsAddress) Save(c *Client) error { - return c.SavePrivateAddresses(adr) -} - -// Save adr with factom-walletd. -func (adr EsAddress) Save(c *Client) error { - return c.SavePrivateAddresses(adr) -} - -// SavePrivateAddresses saves many adrs with factom-walletd. -func (c *Client) SavePrivateAddresses(adrs ...PrivateAddress) error { - var params walletAddressesSecret - params.Addresses = make([]walletAddressSecret, len(adrs)) - for i, adr := range adrs { - params.Addresses[i].Secret = adr.String() - } - if err := c.WalletdRequest("import-addresses", params, nil); err != nil { - return err - } - return nil -} - -// GetBalance queries factomd for the Factoid Balance for adr. -func (adr FAAddress) GetBalance(c *Client) (uint64, error) { - return c.getBalance("factoid-balance", adr) -} - -// GetBalance queries factomd for the Factoid Balance for adr. -func (adr FsAddress) GetBalance(c *Client) (uint64, error) { - return adr.PublicAddress().GetBalance(c) -} - -// GetBalance queries factomd for the Entry Credit Balance for adr. -func (adr ECAddress) GetBalance(c *Client) (uint64, error) { - return c.getBalance("entry-credit-balance", adr) -} - -// GetBalance queries factomd for the Entry Credit Balance for adr. -func (adr EsAddress) GetBalance(c *Client) (uint64, error) { - return adr.PublicAddress().GetBalance(c) -} - -type getBalanceParams struct { - Adr Address `json:"address"` -} -type balanceResult struct{ Balance uint64 } - -func (c *Client) getBalance(method string, adr Address) (uint64, error) { - var result balanceResult - params := getBalanceParams{Adr: adr} - if err := c.FactomdRequest(method, params, &result); err != nil { - return 0, err - } - return result.Balance, nil -} - -// Remove adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. -func (adr FAAddress) Remove(c *Client) error { - return c.RemoveAddress(adr) -} - -// Remove adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. -func (adr FsAddress) Remove(c *Client) error { - return adr.PublicAddress().Remove(c) -} - -// Remove adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. -func (adr ECAddress) Remove(c *Client) error { - return c.RemoveAddress(adr) -} - -// Remove adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. -func (adr EsAddress) Remove(c *Client) error { - return adr.PublicAddress().Remove(c) -} - -// RemoveAddress removes adr from factom-walletd. WARNING: THIS IS DESTRUCTIVE. -func (c *Client) RemoveAddress(adr Address) error { - params := walletAddress{Address: adr.PublicAddress()} - if err := c.WalletdRequest("remove-address", params, nil); err != nil { - return err - } - return nil -} - -// PublicAddress returns adr as an Address. -func (adr FAAddress) PublicAddress() Address { - return adr -} - -// PublicAddress returns the FAAddress corresponding to adr as an Address. -func (adr FsAddress) PublicAddress() Address { - return adr.FAAddress() -} - -// PublicAddress returns adr as an Address. -func (adr ECAddress) PublicAddress() Address { - return adr -} - -// PublicAddress returns the ECAddress corresponding to adr as an Address. -func (adr EsAddress) PublicAddress() Address { - return adr.ECAddress() -} - -// FAAddress returns the FAAddress corresponding to adr. -func (adr FsAddress) FAAddress() FAAddress { - return adr.RCDHash() -} - -// ECAddress returns the ECAddress corresponding to adr. -func (adr EsAddress) ECAddress() (ec ECAddress) { - copy(ec[:], adr.PublicKey()) - return -} - -// RCDHash returns the RCD hash encoded in adr. -func (adr FAAddress) RCDHash() [sha256.Size]byte { - return adr -} - -// RCDHash computes the RCD hash corresponding to adr. -func (adr FsAddress) RCDHash() [sha256.Size]byte { - return sha256d(adr.RCD()) -} - -// sha256( sha256( data ) ) -func sha256d(data []byte) [sha256.Size]byte { - hash := sha256.Sum256(data) - return sha256.Sum256(hash[:]) -} - -const ( - // RCDType is the magic number identifying the currenctly accepted RCD. - RCDType byte = 0x01 - // RCDSize is the size of the RCD. - RCDSize = ed25519.PublicKeySize + 1 - // SignatureSize is the size of the ed25519 signatures. - SignatureSize = ed25519.SignatureSize -) - -// RCD computes the RCD for adr. -func (adr FsAddress) RCD() []byte { - return append([]byte{RCDType}, adr.PublicKey()[:]...) -} - -// PublicKey returns the ed25519.PublicKey for adr. -func (adr ECAddress) PublicKey() ed25519.PublicKey { - return adr[:] -} - -// PublicKey computes the ed25519.PublicKey for adr. -func (adr EsAddress) PublicKey() ed25519.PublicKey { - return adr.PrivateKey().Public().(ed25519.PublicKey) -} - -// PublicKey computes the ed25519.PublicKey for adr. -func (adr FsAddress) PublicKey() ed25519.PublicKey { - return adr.PrivateKey().Public().(ed25519.PublicKey) -} - -// PrivateKey returns the ed25519.PrivateKey for adr. -func (adr FsAddress) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(adr[:]) -} - -// PrivateKey returns the ed25519.PrivateKey for adr. -func (adr EsAddress) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(adr[:]) -} - -// Scan implements sql.Scanner for adr using Bytes32.Scan. The FAAddress type -// is not encoded and is assumed. -func (adr *FAAddress) Scan(v interface{}) error { - return (*Bytes32)(adr).Scan(v) -} - -// Value implements driver.Valuer for adr using Bytes32.Value. The FAAddress -// type is not encoded. -func (adr FAAddress) Value() (driver.Value, error) { - return (Bytes32)(adr).Value() -} - -var _ sql.Scanner = &FAAddress{} -var _ driver.Valuer = &FAAddress{} diff --git a/factom/address_test.go b/factom/address_test.go deleted file mode 100644 index 78804e5..0000000 --- a/factom/address_test.go +++ /dev/null @@ -1,382 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - // Valid Test Addresses generated by factom-walletd - // OBVIOUSLY NEVER USE THESE FOR ANY FUNDS! - FAAddressStr = "FA2PdKfzGP5XwoSbeW1k9QunCHwC8DY6d8xgEdfm57qfR31nTueb" - FsAddressStr = "Fs1ipNRjEXcWj8RUn1GRLMJYVoPFBL1yw9rn6sCxWGcxciC4HdPd" - ECAddressStr = "EC2Pawhv7uAiKFQeLgaqfRhzk5o9uPVY8Ehjh8DnLXENosvYTT26" - EsAddressStr = "Es2tFRhAqHnydaygVAR6zbpWTQXUDaXy1JHWJugQXnYavS8ssQQE" -) - -type addressUnmarshalJSONTest struct { - Name string - Adr Address - ExpAdr Address - Data string - Err string -} - -var addressUnmarshalJSONTests = []addressUnmarshalJSONTest{{ - Name: "valid FA", - Data: fmt.Sprintf("%q", FAAddressStr), - Adr: new(FAAddress), - ExpAdr: func() *FAAddress { - adr, _ := NewFsAddress(FsAddressStr) - pub := adr.FAAddress() - return &pub - }(), -}, { - Name: "valid Fs", - Data: fmt.Sprintf("%q", FsAddressStr), - Adr: new(FsAddress), - ExpAdr: func() *FsAddress { - adr, _ := NewFsAddress(FsAddressStr) - return &adr - }(), -}, { - Name: "valid EC", - Data: fmt.Sprintf("%q", ECAddressStr), - Adr: new(ECAddress), - ExpAdr: func() *ECAddress { - adr, _ := NewEsAddress(EsAddressStr) - pub := adr.ECAddress() - return &pub - }(), -}, { - Name: "valid Es", - Data: fmt.Sprintf("%q", EsAddressStr), - Adr: new(EsAddress), - ExpAdr: func() *EsAddress { - adr, _ := NewEsAddress(EsAddressStr) - return &adr - }(), -}, { - Name: "invalid type", - Data: `{}`, - Err: "json: cannot unmarshal object into Go value of type string", -}, { - Name: "invalid type", - Data: `5.5`, - Err: "json: cannot unmarshal number into Go value of type string", -}, { - Name: "invalid type", - Data: `["hello"]`, - Err: "json: cannot unmarshal array into Go value of type string", -}, { - Name: "invalid length", - Data: fmt.Sprintf("%q", FAAddressStr[0:len(FAAddressStr)-1]), - Err: "invalid length", -}, { - Name: "invalid length", - Data: fmt.Sprintf("%q", FAAddressStr+"Q"), - Err: "invalid length", -}, { - Name: "invalid prefix", - Data: fmt.Sprintf("%q", func() string { - adr, _ := NewFAAddress(FAAddressStr) - return adr.payload().StringPrefix([]byte{0x50, 0x50}) - }()), - Err: "invalid prefix", -}, { - Name: "invalid prefix", - Data: fmt.Sprintf("%q", FsAddressStr), - Err: "invalid prefix", -}, { - Name: "invalid symbol/FA", - Data: fmt.Sprintf("%q", FAAddressStr[0:len(FAAddressStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - Adr: new(FAAddress), - ExpAdr: new(FAAddress), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", FAAddressStr[0:len(FAAddressStr)-1]+"e"), - Err: "checksum error", - Adr: new(FAAddress), - ExpAdr: new(FAAddress), -}} - -func testAddressUnmarshalJSON(t *testing.T, test addressUnmarshalJSONTest) { - err := json.Unmarshal([]byte(test.Data), test.Adr) - assert := assert.New(t) - if len(test.Err) > 0 { - assert.EqualError(err, test.Err) - return - } - assert.NoError(err) - assert.Equal(test.ExpAdr, test.Adr) -} - -func TestAddress(t *testing.T) { - for _, test := range addressUnmarshalJSONTests { - if test.Adr != nil { - t.Run("UnmarshalJSON/"+test.Name, func(t *testing.T) { - testAddressUnmarshalJSON(t, test) - }) - continue - } - test.ExpAdr, test.Adr = &FAAddress{}, &FAAddress{} - t.Run("UnmarshalJSON/FA", func(t *testing.T) { - testAddressUnmarshalJSON(t, test) - }) - test.ExpAdr, test.Adr = &ECAddress{}, &ECAddress{} - t.Run("UnmarshalJSON/EC", func(t *testing.T) { - testAddressUnmarshalJSON(t, test) - }) - } - - fa, _ := NewFAAddress(FAAddressStr) - fs, _ := NewFsAddress(FsAddressStr) - ec, _ := NewECAddress(ECAddressStr) - es, _ := NewEsAddress(EsAddressStr) - strToAdr := map[string]Address{FAAddressStr: fa, FsAddressStr: fs, - ECAddressStr: ec, EsAddressStr: es} - for adrStr, adr := range strToAdr { - t.Run("MarshalJSON/"+adr.PrefixString(), func(t *testing.T) { - data, err := json.Marshal(adr) - assert := assert.New(t) - assert.NoError(err) - assert.Equal(fmt.Sprintf("%q", adrStr), string(data)) - }) - t.Run("Payload/"+adr.PrefixString(), func(t *testing.T) { - assert.EqualValues(t, adr, adr.Payload()) - }) - } - - t.Run("FsAddress", func(t *testing.T) { - pub, _ := NewFAAddress(FAAddressStr) - priv, _ := NewFsAddress(FsAddressStr) - assert := assert.New(t) - assert.Equal(pub, priv.FAAddress()) - assert.Equal(pub.PublicAddress(), priv.PublicAddress()) - assert.Equal(pub.RCDHash(), priv.RCDHash(), "RCDHash") - }) - t.Run("EsAddress", func(t *testing.T) { - pub, _ := NewECAddress(ECAddressStr) - priv, _ := NewEsAddress(EsAddressStr) - assert := assert.New(t) - assert.Equal(pub, priv.ECAddress()) - assert.Equal(pub.PublicAddress(), priv.PublicAddress()) - }) - - t.Run("New", func(t *testing.T) { - for _, adrStr := range []string{FAAddressStr, FsAddressStr, - ECAddressStr, EsAddressStr} { - t.Run(adrStr, func(t *testing.T) { - assert := assert.New(t) - adr, err := NewAddress(adrStr) - assert.NoError(err) - assert.Equal(adrStr, fmt.Sprintf("%v", adr)) - }) - t.Run("Public/"+adrStr, func(t *testing.T) { - assert := assert.New(t) - adr, err := NewPublicAddress(adrStr) - if adrStr[1] == 's' { - assert.EqualError(err, "invalid prefix") - return - } - assert.NoError(err) - assert.Equal(adrStr, fmt.Sprintf("%v", adr)) - }) - t.Run("Private/"+adrStr, func(t *testing.T) { - assert := assert.New(t) - adr, err := NewPrivateAddress(adrStr) - if adrStr[1] != 's' { - assert.EqualError(err, "invalid prefix") - return - } - assert.NoError(err) - assert.Equal(adrStr, fmt.Sprintf("%v", adr)) - }) - } - - t.Run("invalid length", func(t *testing.T) { - assert := assert.New(t) - - _, err := NewAddress("too short") - assert.EqualError(err, "invalid length") - - _, err = NewPrivateAddress("too short") - assert.EqualError(err, "invalid length") - - _, err = NewPublicAddress("too short") - assert.EqualError(err, "invalid length") - }) - - t.Run("unrecognized prefix", func(t *testing.T) { - adr, _ := NewFAAddress(FAAddressStr) - adrStr := adr.payload().StringPrefix([]byte{0x50, 0x50}) - assert := assert.New(t) - - _, err := NewAddress(adrStr) - assert.EqualError(err, "unrecognized prefix") - - _, err = NewPrivateAddress(adrStr) - assert.EqualError(err, "unrecognized prefix") - - _, err = NewPublicAddress(adrStr) - assert.EqualError(err, "unrecognized prefix") - }) - }) - - t.Run("Generate/Fs", func(t *testing.T) { - var err error - fs, err = GenerateFsAddress() - assert.NoError(t, err) - }) - t.Run("Generate/Es", func(t *testing.T) { - var err error - es, err = GenerateEsAddress() - assert.NoError(t, err) - }) - - c := NewClient() - t.Run("Save/Fs", func(t *testing.T) { - err := fs.Save(c) - assert.NoError(t, err) - }) - t.Run("Save/Es", func(t *testing.T) { - err := es.Save(c) - assert.NoError(t, err) - }) - - t.Run("GetPrivateAddress/Fs", func(t *testing.T) { - assert := assert.New(t) - _, err := fs.GetPrivateAddress(nil) - assert.NoError(err) - - fa = fs.FAAddress() - newFs, err := fa.GetPrivateAddress(c) - assert.NoError(err) - assert.Equal(fs, newFs) - }) - t.Run("GetPrivateAddress/Es", func(t *testing.T) { - assert := assert.New(t) - _, err := es.GetPrivateAddress(nil) - assert.NoError(err) - - ec = es.ECAddress() - newEs, err := ec.GetPrivateAddress(c) - assert.NoError(err) - assert.Equal(es, newEs) - assert.Equal(ec.PublicKey(), es.PublicKey()) - }) - - t.Run("GetAddresses", func(t *testing.T) { - adrs, err := c.GetAddresses() - assert := assert.New(t) - assert.NoError(err) - assert.NotEmpty(adrs) - }) - t.Run("GetPrivateAddresses", func(t *testing.T) { - adrs, err := c.GetPrivateAddresses() - assert := assert.New(t) - assert.NoError(err) - assert.NotEmpty(adrs) - }) - t.Run("GetFAAddresses", func(t *testing.T) { - adrs, err := c.GetFAAddresses() - assert := assert.New(t) - assert.NoError(err) - assert.NotEmpty(adrs) - }) - t.Run("GetFsAddresses", func(t *testing.T) { - adrs, err := c.GetFsAddresses() - assert := assert.New(t) - assert.NoError(err) - assert.NotEmpty(adrs) - }) - t.Run("GetECAddresses", func(t *testing.T) { - adrs, err := c.GetECAddresses() - assert := assert.New(t) - assert.NoError(err) - assert.NotEmpty(adrs) - }) - t.Run("GetEsAddresses", func(t *testing.T) { - adrs, err := c.GetEsAddresses() - assert := assert.New(t) - assert.NoError(err) - assert.NotEmpty(adrs) - }) - - for _, adr := range strToAdr { - t.Run("GetBalance/"+adr.PrefixString(), func(t *testing.T) { - balance, err := adr.GetBalance(c) - assert := assert.New(t) - assert.NoError(err) - assert.Equal(uint64(0), balance) - }) - } - fundedEC, _ := NewECAddress("EC1zANmWuEMYoH6VizJg6uFaEdi8Excn1VbLN99KRuxh3GSvB7YQ") - t.Run("GetBalance/"+fundedEC.String(), func(t *testing.T) { - balance, err := fundedEC.GetBalance(c) - assert := assert.New(t) - assert.NoError(err) - assert.NotEqual(uint64(0), balance) - }) - - t.Run("Remove/Fs", func(t *testing.T) { - err := fs.Remove(c) - assert.NoError(t, err) - }) - t.Run("Remove/Es", func(t *testing.T) { - err := es.Remove(c) - assert.NoError(t, err) - }) - - t.Run("Scan", func(t *testing.T) { - var adr FAAddress - err := adr.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = adr.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = adr.Scan(in) - assert.NoError(err) - assert.EqualValues(in, adr[:]) - }) - - t.Run("Value", func(t *testing.T) { - var adr FAAddress - adr[0] = 0xff - val, err := adr.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(adr[:], val) - }) - -} diff --git a/factom/bytes.go b/factom/bytes.go deleted file mode 100644 index a9aa144..0000000 --- a/factom/bytes.go +++ /dev/null @@ -1,161 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "database/sql" - "database/sql/driver" - "encoding/hex" - "encoding/json" - "fmt" -) - -// Bytes32 implements json.Marshaler and json.Unmarshaler to encode and decode -// strings with exactly 32 bytes of hex encoded data, such as Chain IDs and -// KeyMRs. -type Bytes32 [32]byte - -// Bytes implements json.Marshaler and json.Unmarshaler to encode and decode -// strings with hex encoded data, such as an Entry's External IDs or content. -type Bytes []byte - -// NewBytes32 allocates a new Bytes32 object with the first 32 bytes of data -// contained in s32. -func NewBytes32(s32 []byte) *Bytes32 { - b32 := new(Bytes32) - copy(b32[:], s32) - return b32 -} - -// NewBytes32FromString allocates a new Bytes32 object with the hex encoded -// string data contained in s32. -func NewBytes32FromString(s32 string) *Bytes32 { - b32 := new(Bytes32) - b32.Set(s32) - return b32 -} - -// Set decodes a string with exactly 32 bytes of hex encoded data. -func (b *Bytes32) Set(hexStr string) error { - if len(hexStr) == 0 { - return nil - } - if len(hexStr) != hex.EncodedLen(len(b)) { - return fmt.Errorf("invalid length") - } - if _, err := hex.Decode(b[:], []byte(hexStr)); err != nil { - return err - } - return nil -} - -// NewBytesFromString makes a new Bytes object with the hex encoded string data -// contained in s. -func NewBytesFromString(s string) Bytes { - var b Bytes - b.Set(s) - return b -} - -// Set decodes a string with hex encoded data. -func (b *Bytes) Set(hexStr string) error { - *b = make(Bytes, hex.DecodedLen(len(hexStr))) - if _, err := hex.Decode(*b, []byte(hexStr)); err != nil { - return err - } - return nil -} - -// UnmarshalJSON decodes a JSON string with exactly 32 bytes of hex encoded -// data. -func (b *Bytes32) UnmarshalJSON(data []byte) error { - var hexStr string - if err := json.Unmarshal(data, &hexStr); err != nil { - return err - } - return b.Set(hexStr) -} - -// UnmarshalJSON decodes a JSON string with hex encoded data. -func (b *Bytes) UnmarshalJSON(data []byte) error { - var hexStr string - if err := json.Unmarshal(data, &hexStr); err != nil { - return err - } - return b.Set(hexStr) -} - -// String encodes b as a hex string. -func (b Bytes32) String() string { - return hex.EncodeToString(b[:]) -} - -// String encodes b as a hex string. -func (b Bytes) String() string { - return hex.EncodeToString(b[:]) -} - -// Type returns "Bytes32". Satisfies pflag.Value interface. -func (b Bytes32) Type() string { - return "Bytes32" -} - -// Type returns "Bytes". Satisfies pflag.Value interface. -func (b Bytes) Type() string { - return "Bytes" -} - -// MarshalJSON encodes b as a hex JSON string. -func (b Bytes32) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf("%#v", b.String())), nil -} - -// MarshalJSON encodes b as a hex JSON string. -func (b Bytes) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf("%#v", b.String())), nil -} - -// Scan expects v to be a byte slice with exactly 32 bytes of data. -func (b *Bytes32) Scan(v interface{}) error { - data, ok := v.([]byte) - if !ok { - return fmt.Errorf("invalid type") - } - if len(data) != 32 { - return fmt.Errorf("invalid length") - } - copy(b[:], data) - return nil -} - -// Value expects b to be a byte slice with exactly 32 bytes of data. -func (b Bytes32) Value() (driver.Value, error) { - return b[:], nil -} - -var _ sql.Scanner = &Bytes32{} -var _ driver.Valuer = Bytes32{} - -func (b Bytes32) IsZero() bool { - return b == Bytes32{} -} diff --git a/factom/bytes_test.go b/factom/bytes_test.go deleted file mode 100644 index be788d7..0000000 --- a/factom/bytes_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "encoding/json" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - JSONBytesInvalidTypes = []string{`{}`, `5.5`, `["hello"]`} - JSONBytes32InvalidLengths = []string{ - `"00"`, - `"000000000000000000000000000000000000000000000000000000000000000000"`} - JSONBytesInvalidSymbol = `"x000000000000000000000000000000000000000000000000000000000000000"` - JSONBytes32Valid = `"da56930e8693fb7c0a13aac4d01cf26184d760f2fd92d2f0a62aa630b1a25fa7"` -) - -type unmarshalJSONTest struct { - Name string - Data string - Err string - Un interface { - json.Unmarshaler - json.Marshaler - } - Exp interface { - json.Unmarshaler - json.Marshaler - } -} - -var unmarshalJSONtests = []unmarshalJSONTest{{ - Name: "Bytes32/valid", - Data: `"DA56930e8693fb7c0a13aac4d01cf26184d760f2fd92d2f0a62aa630b1a25fa7"`, - Un: new(Bytes32), - Exp: &Bytes32{0xDA, 0x56, 0x93, 0x0e, 0x86, 0x93, 0xfb, 0x7c, 0x0a, 0x13, - 0xaa, 0xc4, 0xd0, 0x1c, 0xf2, 0x61, 0x84, 0xd7, 0x60, 0xf2, 0xfd, - 0x92, 0xd2, 0xf0, 0xa6, 0x2a, 0xa6, 0x30, 0xb1, 0xa2, 0x5f, 0xa7}, -}, { - Name: "Bytes/valid", - Data: `"DA56930e8693fb7c0a13aac4d01cf26184d760f2fd92d2f0a62aa630b1a25fa7"`, - Un: new(Bytes), - Exp: &Bytes{0xDA, 0x56, 0x93, 0x0e, 0x86, 0x93, 0xfb, 0x7c, 0x0a, 0x13, - 0xaa, 0xc4, 0xd0, 0x1c, 0xf2, 0x61, 0x84, 0xd7, 0x60, 0xf2, 0xfd, - 0x92, 0xd2, 0xf0, 0xa6, 0x2a, 0xa6, 0x30, 0xb1, 0xa2, 0x5f, 0xa7}, -}, { - Name: "Bytes32/valid", - Data: `"0000000000000000000000000000000000000000000000000000000000000000"`, - Un: new(Bytes32), - Exp: &Bytes32{}, -}, { - Name: "Bytes/valid", - Data: `"0000000000000000000000000000000000000000000000000000000000000000"`, - Un: new(Bytes), - Exp: func() *Bytes { b := make(Bytes, 32); return &b }(), -}, { - Name: "invalid symbol", - Data: `"DA56930e8693fb7c0a13aac4d01cf26184d760f2fd92d2f0a62aa630b1zxcva7"`, - Err: "encoding/hex: invalid byte: U+007A 'z'", -}, { - Name: "invalid type", - Data: `{}`, - Err: "json: cannot unmarshal object into Go value of type string", -}, { - Name: "invalid type", - Data: `5.5`, - Err: "json: cannot unmarshal number into Go value of type string", -}, { - Name: "invalid type", - Data: `["asdf"]`, - Err: "json: cannot unmarshal array into Go value of type string", -}, { - Name: "too long", - Data: `"DA56930e8693fb7c0a13aac4d01cf26184d760f2fd92d2f0a62aa630b1a25fa71234"`, - Err: "invalid length", - Un: new(Bytes32), -}, { - Name: "too short", - Data: `"DA56930e8693fb7c0a13aac4d01cf26184d760f2fd92d2f0a62aa630b1a25fa71234"`, - Err: "invalid length", - Un: new(Bytes32), -}} - -func testUnmarshalJSON(t *testing.T, test unmarshalJSONTest) { - assert := assert.New(t) - err := test.Un.UnmarshalJSON([]byte(test.Data)) - if len(test.Err) > 0 { - assert.EqualError(err, test.Err) - return - } - assert.NoError(err) - assert.Equal(test.Exp, test.Un) -} - -func TestBytes(t *testing.T) { - for _, test := range unmarshalJSONtests { - if test.Un != nil { - t.Run("UnmarshalJSON/"+test.Name, func(t *testing.T) { - testUnmarshalJSON(t, test) - }) - if test.Exp != nil { - t.Run("MarshalJSON/"+test.Name, func(t *testing.T) { - data, err := test.Un.MarshalJSON() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(strings.ToLower(test.Data), - string(data)) - }) - } - continue - } - test.Un = new(Bytes32) - t.Run("UnmarshalJSON/Bytes32/"+test.Name, func(t *testing.T) { - testUnmarshalJSON(t, test) - }) - test.Un = new(Bytes) - t.Run("UnmarshalJSON/Bytes/"+test.Name, func(t *testing.T) { - testUnmarshalJSON(t, test) - }) - } - - t.Run("Scan", func(t *testing.T) { - var b Bytes32 - err := b.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = b.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = b.Scan(in) - assert.NoError(err) - assert.EqualValues(in, b[:]) - }) - - t.Run("Value", func(t *testing.T) { - var b Bytes32 - b[0] = 0xff - val, err := b.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(b[:], val) - }) - - t.Run("IsZero()", func(t *testing.T) { - assert.True(t, Bytes32{}.IsZero()) - assert.False(t, Bytes32{0: 1}.IsZero()) - }) -} diff --git a/factom/client.go b/factom/client.go deleted file mode 100644 index 3befc4e..0000000 --- a/factom/client.go +++ /dev/null @@ -1,76 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "fmt" - "time" - - jrpc "github.com/AdamSLevy/jsonrpc2/v11" -) - -// Client makes RPC requests to factomd's and factom-walletd's APIs. Client -// embeds two jsonrpc2.Clients, and thus also two http.Client, one for requests -// to factomd and one for requests to factom-walletd. Use jsonrpc2.Client's -// BasicAuth settings to set up BasicAuth and http.Client's transport settings -// to configure TLS. -type Client struct { - Factomd jrpc.Client - FactomdServer string - Walletd jrpc.Client - WalletdServer string -} - -// Defaults for the factomd and factom-walletd endpoints. -const ( - FactomdDefault = "http://localhost:8088" - WalletdDefault = "http://localhost:8089" -) - -// NewClient returns a pointer to a Client initialized with the default -// localhost endpoints for factomd and factom-walletd, and 15 second timeouts -// for each of the http.Clients. -func NewClient() *Client { - c := &Client{FactomdServer: FactomdDefault, WalletdServer: WalletdDefault} - c.Factomd.Timeout = 20 * time.Second - c.Walletd.Timeout = 10 * time.Second - return c -} - -// FactomdRequest makes a request to factomd's v2 API. -func (c *Client) FactomdRequest(method string, params, result interface{}) error { - url := c.FactomdServer + "/v2" - if c.Factomd.DebugRequest { - fmt.Println("factomd:", url) - } - return c.Factomd.Request(url, method, params, result) -} - -// WalletdRequest makes a request to factom-walletd's v2 API. -func (c *Client) WalletdRequest(method string, params, result interface{}) error { - url := c.WalletdServer + "/v2" - if c.Walletd.DebugRequest { - fmt.Println("factom-walletd:", url) - } - return c.Walletd.Request(url, method, params, result) -} diff --git a/factom/dblock.go b/factom/dblock.go deleted file mode 100644 index fbadcf1..0000000 --- a/factom/dblock.go +++ /dev/null @@ -1,351 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "bytes" - "crypto/sha256" - "encoding/binary" - "encoding/json" - "fmt" - "math" - "sort" - "time" - - merkle "github.com/AdamSLevy/go-merkle" -) - -var ( - adminBlockChainID = Bytes32{31: 0x0a} - entryCreditBlockChainID = Bytes32{31: 0x0c} - factoidBlockChainID = Bytes32{31: 0x0f} -) - -// DBlock represents a Factom Directory Block. -type DBlock struct { - KeyMR *Bytes32 `json:"keymr"` - - FullHash *Bytes32 `json:"dbhash"` - - Header DBlockHeader `json:"header"` - - // DBlock.Get populates EBlocks with their ChainID and KeyMR. - EBlocks []EBlock `json:"dbentries,omitempty"` -} - -type DBlockHeader struct { - NetworkID NetworkID `json:"networkid"` - - BodyMR *Bytes32 `json:"bodymr"` - PrevKeyMR *Bytes32 `json:"prevkeymr"` - PrevFullHash *Bytes32 `json:"prevfullhash"` - - Height uint32 `json:"dbheight"` - - Timestamp time.Time `json:"-"` -} - -type dBH DBlockHeader -type dBHs struct { - *dBH - NetworkID uint32 `json:"networkid"` - Timestamp int64 `json:"timestamp"` -} - -func (dbh *DBlockHeader) UnmarshalJSON(data []byte) error { - d := dBHs{dBH: (*dBH)(dbh)} - if err := json.Unmarshal(data, &d); err != nil { - return err - } - dbh.Timestamp = time.Unix(d.Timestamp*60, 0) - binary.BigEndian.PutUint32(dbh.NetworkID[:], d.NetworkID) - return nil -} - -func (dbh *DBlockHeader) MarshalJSON() ([]byte, error) { - d := dBHs{ - (*dBH)(dbh), - binary.BigEndian.Uint32(dbh.NetworkID[:]), - dbh.Timestamp.Unix() / 60, - } - return json.Marshal(d) -} - -// IsPopulated returns true if db has already been successfully populated by a -// call to Get. IsPopulated returns false if db.EBlocks is nil. -func (db DBlock) IsPopulated() bool { - return len(db.EBlocks) > 0 && - db.Header.BodyMR != nil && - db.Header.PrevKeyMR != nil && - db.Header.PrevFullHash != nil -} - -// Get queries factomd for the Directory Block at db.Header.Height. After a -// successful call, the EBlocks will all have their ChainID and KeyMR, but not -// their Entries. Call Get on the EBlocks individually to populate their -// Entries. -func (db *DBlock) Get(c *Client) (err error) { - if db.IsPopulated() { - return nil - } - defer func() { - if err != nil { - return - } - var keyMR Bytes32 - if keyMR, err = db.ComputeKeyMR(); err != nil { - return - } - if *db.KeyMR != keyMR { - err = fmt.Errorf("invalid key merkle root") - return - } - }() - - if db.KeyMR != nil { - params := struct { - Hash *Bytes32 `json:"hash"` - }{Hash: db.KeyMR} - var result struct { - Data Bytes `json:"data"` - } - if err := c.FactomdRequest("raw-data", params, &result); err != nil { - return err - } - return db.UnmarshalBinary(result.Data) - } - - params := struct { - Height uint32 `json:"height"` - }{db.Header.Height} - result := struct { - DBlock *DBlock - }{DBlock: db} - if err := c.FactomdRequest("dblock-by-height", params, &result); err != nil { - return err - } - - for ebi := range db.EBlocks { - eb := &db.EBlocks[ebi] - eb.Timestamp = db.Header.Timestamp - eb.Height = db.Header.Height - } - - return nil -} - -const ( - DBlockHeaderLen = 1 + // [Version byte (0x00)] - 4 + // NetworkID - 32 + // BodyMR - 32 + // PrevKeyMR - 32 + // PrevFullHash - 4 + // Timestamp - 4 + // DB Height - 4 // EBlock Count - - DBlockEBlockLen = 32 + // ChainID - 32 // KeyMR - - DBlockMinBodyLen = DBlockEBlockLen + // Admin Block - DBlockEBlockLen + // EC Block - DBlockEBlockLen // FCT Block - DBlockMinTotalLen = DBlockHeaderLen + DBlockMinBodyLen - - DBlockMaxBodyLen = math.MaxUint32 * DBlockEBlockLen - DBlockMaxTotalLen = DBlockHeaderLen + DBlockMaxBodyLen -) - -// UnmarshalBinary unmarshals raw directory block data. -// -// Header -// [Version byte (0x00)] + -// [NetworkID (4 bytes)] + -// [BodyMR (Bytes32)] + -// [PrevKeyMR (Bytes32)] + -// [PrevFullHash (Bytes32)] + -// [Timestamp (4 bytes)] + -// [DB Height (4 bytes)] + -// [EBlock Count (4 bytes)] -// -// Body -// [Admin Block ChainID (Bytes32{31:0x0a})] + -// [Admin Block LookupHash (Bytes32)] + -// [EC Block ChainID (Bytes32{31:0x0c})] + -// [EC Block HeaderHash (Bytes32)] + -// [FCT Block ChainID (Bytes32{31:0x0f})] + -// [FCT Block KeyMR (Bytes32)] + -// [ChainID 0 (Bytes32)] + -// [KeyMR 0 (Bytes32)] + -// ... + -// [ChainID N (Bytes32)] + -// [KeyMR N (Bytes32)] + -// -// https://github.com/FactomProject/FactomDocs/blob/master/factomDataStructureDetails.md#directory-block -func (db *DBlock) UnmarshalBinary(data []byte) error { - if len(data) < DBlockMinTotalLen { - return fmt.Errorf("insufficient length") - } - if len(data) > DBlockMaxTotalLen { - return fmt.Errorf("invalid length") - } - if data[0] != 0x00 { - return fmt.Errorf("invalid version byte") - } - i := 1 - i += copy(db.Header.NetworkID[:], data[i:i+len(db.Header.NetworkID)]) - db.Header.BodyMR = new(Bytes32) - i += copy(db.Header.BodyMR[:], data[i:i+len(db.Header.BodyMR)]) - db.Header.PrevKeyMR = new(Bytes32) - i += copy(db.Header.PrevKeyMR[:], data[i:i+len(db.Header.PrevKeyMR)]) - db.Header.PrevFullHash = new(Bytes32) - i += copy(db.Header.PrevFullHash[:], data[i:i+len(db.Header.PrevFullHash)]) - db.Header.Timestamp = time.Unix(int64(binary.BigEndian.Uint32(data[i:i+4]))*60, 0) - i += 4 - db.Header.Height = binary.BigEndian.Uint32(data[i : i+4]) - i += 4 - ebsLen := int(binary.BigEndian.Uint32(data[i : i+4])) - i += 4 - if len(data[i:]) < ebsLen*DBlockEBlockLen { - return fmt.Errorf("insufficient length") - } - db.EBlocks = make([]EBlock, ebsLen) - var lastChainID Bytes32 - for ebi := range db.EBlocks { - eb := &db.EBlocks[ebi] - // Ensure that EBlocks are ordered by Chain ID with no - // duplicates. - if bytes.Compare(data[i:i+len(eb.ChainID)], lastChainID[:]) <= 0 { - return fmt.Errorf("out of order or duplicate Chain ID") - } - eb.ChainID = new(Bytes32) - i += copy(eb.ChainID[:], data[i:i+len(eb.ChainID)]) - lastChainID = *eb.ChainID - eb.KeyMR = new(Bytes32) - i += copy(eb.KeyMR[:], data[i:i+len(eb.KeyMR)]) - - eb.Timestamp = db.Header.Timestamp - eb.Height = db.Header.Height - } - return nil -} - -func (db *DBlock) MarshalBinary() ([]byte, error) { - data, err := db.MarshalBinaryHeader() - if err != nil { - return nil, err - } - i := DBlockHeaderLen - for _, eb := range db.EBlocks { - i += copy(data[i:], eb.ChainID[:]) - i += copy(data[i:], eb.KeyMR[:]) - } - return data, nil -} - -func (db *DBlock) MarshalBinaryHeader() ([]byte, error) { - if !db.IsPopulated() { - return nil, fmt.Errorf("not populated") - } - totalLen := db.MarshalBinaryLen() - if totalLen > DBlockMaxTotalLen { - return nil, fmt.Errorf("too many EBlocks") - } - data := make([]byte, totalLen) - i := 1 // Skip version byte - i += copy(data[i:], db.Header.NetworkID[:]) - i += copy(data[i:], db.Header.BodyMR[:]) - i += copy(data[i:], db.Header.PrevKeyMR[:]) - i += copy(data[i:], db.Header.PrevFullHash[:]) - binary.BigEndian.PutUint32(data[i:], uint32(db.Header.Timestamp.Unix()/60)) - i += 4 - binary.BigEndian.PutUint32(data[i:], db.Header.Height) - i += 4 - binary.BigEndian.PutUint32(data[i:], uint32(len(db.EBlocks))) - i += 4 - return data, nil -} - -func (db *DBlock) MarshalBinaryLen() int { - return DBlockHeaderLen + len(db.EBlocks)*DBlockEBlockLen -} - -func (db DBlock) ComputeBodyMR() (Bytes32, error) { - blocks := make([][]byte, len(db.EBlocks)) - for i, eb := range db.EBlocks { - blocks[i] = make([]byte, len(eb.ChainID)+len(eb.KeyMR)) - j := copy(blocks[i], eb.ChainID[:]) - copy(blocks[i][j:], eb.KeyMR[:]) - } - tree := merkle.NewTreeWithOpts(merkle.TreeOptions{DoubleOddNodes: true}) - if err := tree.Generate(blocks, sha256.New()); err != nil { - return Bytes32{}, err - } - root := tree.Root() - var bodyMR Bytes32 - copy(bodyMR[:], root.Hash) - return bodyMR, nil -} - -func (db DBlock) ComputeFullHash() (Bytes32, error) { - data, err := db.MarshalBinary() - if err != nil { - return Bytes32{}, err - } - return sha256.Sum256(data), nil -} - -func (db DBlock) ComputeHeaderHash() (Bytes32, error) { - header, err := db.MarshalBinaryHeader() - if err != nil { - return Bytes32{}, err - } - return sha256.Sum256(header[:DBlockHeaderLen]), nil -} - -func (db DBlock) ComputeKeyMR() (Bytes32, error) { - headerHash, err := db.ComputeHeaderHash() - if err != nil { - return Bytes32{}, err - } - bodyMR, err := db.ComputeBodyMR() - if err != nil { - return Bytes32{}, err - } - data := make([]byte, len(headerHash)+len(bodyMR)) - i := copy(data, headerHash[:]) - copy(data[i:], bodyMR[:]) - return sha256.Sum256(data), nil -} - -// EBlock efficiently finds and returns the *EBlock in db.EBlocks for the given -// chainID, if it exists. Otherwise, EBlock returns nil. -func (db DBlock) EBlock(chainID Bytes32) *EBlock { - ei := sort.Search(len(db.EBlocks), func(i int) bool { - return bytes.Compare(db.EBlocks[i].ChainID[:], chainID[:]) >= 0 - }) - if ei < len(db.EBlocks) && *db.EBlocks[ei].ChainID == chainID { - return &db.EBlocks[ei] - } - return nil -} diff --git a/factom/dblock_eblock_entry_test.go b/factom/dblock_eblock_entry_test.go deleted file mode 100644 index 2e526d7..0000000 --- a/factom/dblock_eblock_entry_test.go +++ /dev/null @@ -1,246 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom_test - -import ( - "testing" - "time" - - . "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var courtesyNode = "https://courtesy-node.factom.com" - -func TestDataStructures(t *testing.T) { - height := uint32(166587) - c := NewClient() - //c.Factomd.DebugRequest = true - db := &DBlock{} - db.Header.Height = height - t.Run("DBlock", func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - // We should start off unpopulated. - require.False(db.IsPopulated()) - - // A bad URL will cause an error. - c.FactomdServer = "http://example.com" - assert.Error(db.Get(c)) - - c.FactomdServer = courtesyNode - require.NoError(db.Get(c)) - - require.True(db.IsPopulated(), db) - assert.NoError(db.Get(c)) // Take the early exit code path. - - // Validate this DBlock. - assert.Len(db.EBlocks, 7) - assert.Equal(height, db.Header.Height) - for _, eb := range db.EBlocks { - assert.NotNil(eb.ChainID) - assert.NotNil(eb.KeyMR) - } - - dbk := DBlock{KeyMR: db.KeyMR, FullHash: db.FullHash} - require.NoError(dbk.Get(c)) - assert.Equal(*db, dbk) - - params := struct { - Hash *Bytes32 `json:"hash"` - }{Hash: db.KeyMR} - var result struct { - Data Bytes `json:"data"` - } - require.NoError(c.FactomdRequest("raw-data", params, &result)) - - data, err := db.MarshalBinary() - require.NoError(err) - for i := range result.Data { - assert.Equal(result.Data[i], data[i], i) - } - - full, err := dbk.ComputeFullHash() - require.NoError(err, "ComputeFullHash()") - assert.Equal(*db.FullHash, full, "ComputeFullHash()") - - bodyMR, err := dbk.ComputeBodyMR() - require.NoError(err, "ComputeBodyMR()") - assert.Equal(*db.Header.BodyMR, bodyMR, "ComputeBodyMR()") - - keyMR, err := dbk.ComputeKeyMR() - require.NoError(err, "ComputeKeyMR()") - assert.Equal(*db.KeyMR, keyMR, "ComputeKeyMR()") - - eb := &db.EBlocks[len(db.EBlocks)-1] - assert.Equal(eb, db.EBlock(*eb.ChainID)) - assert.Nil(db.EBlock(Bytes32{})) - }) - t.Run("EBlock", func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - // An EBlock without a KeyMR or ChainID should cause an error. - blank := EBlock{} - assert.EqualError(blank.Get(c), "no KeyMR or ChainID specified") - - // We'll use the DBlock from the last test, so it must be - // populated to proceed. - require.True(db.IsPopulated()) - - // This EBlock has multiple entries we can validate against. - // We'll use a pointer here so that we can reuse this EBlock in - // the next test. - eb := &db.EBlocks[4] - - // We start off unpopulated. - require.False(eb.IsPopulated()) - - // A bad URL will cause an error. - c.FactomdServer = "example.com" - assert.Error(eb.Get(c)) - - c.FactomdServer = courtesyNode - require.NoError(eb.Get(c)) - - require.True(eb.IsPopulated()) - assert.NoError(eb.Get(c)) // Take the early exit code path. - - // Validate the entries. - assert.Len(eb.Entries, 5) - assert.Equal(height, eb.Height) - require.NotNil(eb.PrevKeyMR) - for _, e := range eb.Entries { - assert.True(e.ChainID == eb.ChainID) - assert.NotNil(e.Hash) - assert.NotNil(e.Timestamp) - } - - assert.False(eb.IsFirst()) - - // A bad URL will cause an error. - c.FactomdServer = "example.com" - _, err := eb.GetPrevAll(c) - assert.Error(err) - - c.FactomdServer = courtesyNode - ebs, err := eb.GetPrevAll(c) - var first EBlock - if assert.NoError(err) { - assert.Len(ebs, 5) - assert.True(ebs[len(ebs)-1].IsFirst()) - first = ebs[len(ebs)-1].Prev() - assert.Equal(first.KeyMR, ebs[len(ebs)-1].KeyMR, - "Prev() should return a copy of itself if it is first") - assert.Equal(eb.KeyMR, ebs[0].KeyMR) - } - - // Fetch the chain head EBlock via the ChainID. - // First use an invalid ChainID and an invalid URL. - eb2 := EBlock{ChainID: NewBytes32(nil)} - c.FactomdServer = "example.com" - assert.Error(eb2.Get(c)) - assert.Error(eb2.GetFirst(c)) - - c.FactomdServer = courtesyNode - require.Error(eb2.Get(c)) - require.False(eb2.IsPopulated()) - assert.EqualError(eb2.GetFirst(c), - `jsonrpc2.Error{Code:-32009, Message:"Missing Chain Head"}`) - ebs, err = eb2.GetPrevAll(c) - assert.EqualError(err, - `jsonrpc2.Error{Code:-32009, Message:"Missing Chain Head"}`) - assert.Nil(ebs) - - // A valid ChainID should allow it to be populated. - eb2.ChainID = eb.ChainID - require.NoError(eb2.Get(c)) - require.True(eb2.IsPopulated()) - assert.NoError(eb2.GetFirst(c)) - assert.Equal(first.KeyMR, eb2.KeyMR) - - // Make RPC request for this Entry Block. - params := struct { - KeyMR *Bytes32 `json:"hash"` - }{KeyMR: eb2.KeyMR} - var result struct { - Data Bytes `json:"data"` - } - require.NoError(c.FactomdRequest("raw-data", params, &result)) - data, err := eb2.MarshalBinary() - require.NoError(err) - assert.Equal(result.Data, Bytes(data)) - - bodyMR, err := eb2.ComputeBodyMR() - require.NoError(err) - assert.Equal(*eb2.BodyMR, bodyMR) - - keyMR, err := eb2.ComputeKeyMR() - require.NoError(err) - assert.Equal(*eb2.KeyMR, keyMR) - }) - t.Run("Entry", func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - // An EBlock without a KeyMR or ChainID should cause an error. - blank := Entry{} - assert.EqualError(blank.Get(c), "Hash is nil") - - // We'll use the DBlock and EBlock from the last test, so they - // must be populated to proceed. - require.True(db.IsPopulated()) - eb := db.EBlocks[4] - require.True(eb.IsPopulated()) - - e := eb.Entries[0] - // We start off unpopulated. - require.False(e.IsPopulated()) - - // A bad URL will cause an error. - c.FactomdServer = "example.com" - assert.Error(e.Get(c)) - - c.FactomdServer = courtesyNode - require.NoError(e.Get(c)) - - require.True(e.IsPopulated()) - assert.NoError(e.Get(c)) // Take the early exit code path. - - // Validate the entry. - assert.Len(e.ExtIDs, 6) - assert.NotEmpty(e.Content) - assert.Equal(time.Unix(1542223080, 0), e.Timestamp) - hash, err := e.ComputeHash() - assert.NoError(err) - assert.Equal(*e.Hash, hash) - - e = eb.Entries[1] - require.NoError(e.Get(c)) - hash, err = e.ComputeHash() - assert.NoError(err) - assert.Equal(*e.Hash, hash) - }) -} diff --git a/factom/doc.go b/factom/doc.go deleted file mode 100644 index 7fc4487..0000000 --- a/factom/doc.go +++ /dev/null @@ -1,60 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Package factom provides data types corresponding to some of the Factom -// blockchain's data structures, as well as methods on those types for querying -// the data from factomd and factom-walletd's APIs. -// -// All of the Factom data structure types in this package have the Get and -// IsPopulated methods. -// -// Methods that accept a *Client, like those that start with Get, may make -// calls to the factomd or factom-walletd API queries to populate the data in -// the variable on which it is called. The returned error can be checked to see -// if it is a jsonrpc2.Error type, indicating that the networking calls were -// successful, but that there is some error returned by the RPC method. -// -// IsPopulated methods return whether the data in the variable has been -// populated by a successful call to Get. -// -// The DBlock, EBlock and Entry types allow for exploring the Factom -// blockchain. -// -// The Bytes and Bytes32 types are used by other types when JSON marshaling and -// unmarshaling to and from hex encoded data is required. Bytes32 is used for -// Chain IDs and KeyMRs. -// -// The Address interfaces and types allow for working with the four Factom -// address types. -// -// The IDKey interfaces and types allow for working with the id/sk key pairs -// for server identities. -// -// Currently this package supports creating new chains and entries using both -// the factom-walletd "compose" methods, and by locally generating the commit -// and reveal data, if the private entry credit key is available locally. See -// Entry.Create and Entry.ComposeCreate. -// -// This package does not yet support Factoid transactions, nor does it support -// the binary data structures for DBlocks or EBlocks. Additionally, working -// with Identity Chains is not yet supported beyond querying the ID1Key. -package factom diff --git a/factom/eblock.go b/factom/eblock.go deleted file mode 100644 index a8729a0..0000000 --- a/factom/eblock.go +++ /dev/null @@ -1,479 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "bytes" - "crypto/sha256" - "encoding/binary" - "fmt" - "math" - "time" - - merkle "github.com/AdamSLevy/go-merkle" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" -) - -// EBlock represents a Factom Entry Block. -type EBlock struct { - // DBlock.Get populates the ChainID, KeyMR, and Height. - ChainID *Bytes32 `json:"chainid,omitempty"` - KeyMR *Bytes32 `json:"keymr,omitempty"` - - PrevKeyMR *Bytes32 `json:"-"` - - PrevFullHash *Bytes32 `json:"-"` - BodyMR *Bytes32 `json:"-"` - - Height uint32 `json:"-"` - Sequence uint32 `json:"-"` - ObjectCount uint32 `json:"-"` - - Timestamp time.Time `json:"-"` - - // EBlock.Get populates the EBlockHeader.PrevKeyMR and the Entries with - // their Hash and Timestamp. - Entries []Entry `json:"-"` -} - -func (eb EBlock) IsPopulated() bool { - return len(eb.Entries) > 0 && - eb.ChainID != nil && - eb.PrevKeyMR != nil && - eb.PrevFullHash != nil && - eb.BodyMR != nil && - eb.ObjectCount > 1 -} - -// Get queries factomd for the Entry Block corresponding to eb.KeyMR, if not -// nil, and otherwise the Entry Block chain head for eb.ChainID. Either -// eb.KeyMR or eb.ChainID must be not nil or else Get will fail to populate the -// EBlock. After a successful call, EBlockHeader and Entries will be populated. -// Each Entry will be populated with its Hash, Timestamp, ChainID, and Height, -// but not its Content or ExtIDs. Call Get on the individual Entries to -// populate their Content and ExtIDs. -func (eb *EBlock) Get(c *Client) error { - // If the EBlock is already populated then there is nothing to do. - if eb.IsPopulated() { - return nil - } - - // If we don't have a KeyMR, fetch the chain head KeyMR. - if eb.KeyMR == nil { - // If the KeyMR and ChainID are both nil we have nothing to - // query for. - if eb.ChainID == nil { - return fmt.Errorf("no KeyMR or ChainID specified") - } - if err := eb.GetChainHead(c); err != nil { - return err - } - } - - // Make RPC request for this Entry Block. - params := struct { - KeyMR *Bytes32 `json:"hash"` - }{KeyMR: eb.KeyMR} - var result struct { - Data Bytes `json:"data"` - } - if err := c.FactomdRequest("raw-data", params, &result); err != nil { - return err - } - height := eb.Height - if err := eb.UnmarshalBinary(result.Data); err != nil { - return err - } - // Verify height if it was initialized - if height > 0 && height != eb.Height { - return fmt.Errorf("height does not match") - } - keyMR, err := eb.ComputeKeyMR() - if err != nil { - return err - } - if *eb.KeyMR != keyMR { - return fmt.Errorf("invalid KeyMR") - } - return nil -} - -// GetChainHead queries factomd for the latest eb.KeyMR for chain eb.ChainID. -func (eb *EBlock) GetChainHead(c *Client) error { - params := eb - result := struct { - KeyMR *Bytes32 `json:"chainhead"` - ChainInProcessList bool `json:"chaininprocesslist"` - }{} - if err := c.FactomdRequest("chain-head", params, &result); err != nil { - return err - } - if result.KeyMR.IsZero() { - if result.ChainInProcessList { - return jrpc.Error{Message: "new chain in process list"} - } else { - return jrpc.Error{Code: -32009, Message: "Missing Chain Head"} - } - } - eb.KeyMR = result.KeyMR - return nil -} - -// GetEntries calls eb.Get and then calls Get on each Entry in eb.Entries. -func (eb *EBlock) GetEntries(c *Client) error { - if err := eb.Get(c); err != nil { - return err - } - for i := range eb.Entries { - e := &eb.Entries[i] - if err := e.Get(c); err != nil { - return err - } - } - return nil -} - -// IsFirst returns true if this is the first EBlock in its chain, indicated by -// the PrevKeyMR being all zeroes. IsFirst returns false if eb is not populated -// or if the PrevKeyMR is not all zeroes. -func (eb EBlock) IsFirst() bool { - return eb.IsPopulated() && eb.PrevKeyMR.IsZero() -} - -// Prev returns the an EBlock with its KeyMR initialized to eb.PrevKeyMR and -// ChainID initialized to eb.ChainID. If eb is the first Entry Block in the -// chain, then eb is returned. -func (eb EBlock) Prev() EBlock { - if eb.IsFirst() { - return eb - } - return EBlock{ChainID: eb.ChainID, KeyMR: eb.PrevKeyMR} -} - -// GetPrevAll returns a slice of all preceding EBlocks, in order from eb to the -// first EBlock in the chain. So the 0th element of the returned slice is -// always equal to eb. If eb is the first EBlock in the chain, then it is the -// only element in the slice. Like Get, if eb does not have a KeyMR, the chain -// head KeyMR is queried first. -// -// If you are only interested in obtaining the first entry block in eb's chain, -// and not all of the intermediary ones, then use GetFirst. -func (eb EBlock) GetPrevAll(c *Client) ([]EBlock, error) { - return eb.GetPrevUpTo(c, Bytes32{}) -} - -// GetPrevUpTo returns a slice of all preceding EBlocks, in order from eb up -// to, but not including, keyMR. So the 0th element of the returned slice is -// always equal to eb. If *eb.KeyMR == keyMR then nil, nil is returned. If -// *eb.PrevKeyMR == keyMR, then it is the only element in the slice. If the -// beginning of the chain is reached without finding keyMR, an error is -// returned with the fully populated []EBlock. Like Get, if eb does not have a -// KeyMR, the chain head KeyMR is queried first. -func (eb EBlock) GetPrevUpTo(c *Client, keyMR Bytes32) ([]EBlock, error) { - if err := eb.Get(c); err != nil { - return nil, err - } - if *eb.KeyMR == keyMR { - return nil, nil - } - ebs := []EBlock{eb} - for { - if *eb.PrevKeyMR == keyMR { - return ebs, nil - } - if eb.IsFirst() { - return ebs, fmt.Errorf("EBlock{%v} not found prior to EBlock{%v}", - keyMR, eb.KeyMR) - } - eb = eb.Prev() - if err := eb.Get(c); err != nil { - return nil, err - } - ebs = append(ebs, eb) - } -} - -// GetFirst finds the first Entry Block in eb's chain, and populates eb as -// such. -// -// GetFirst differs from GetPrevAll in that it does not allocate any additional -// EBlocks. GetFirst avoids allocating any new EBlocks by reusing eb to -// traverse up to the first entry block. -func (eb *EBlock) GetFirst(c *Client) error { - for ; !eb.IsFirst(); *eb = eb.Prev() { - if err := eb.Get(c); err != nil { - return err - } - } - return nil -} - -const ( - EBlockHeaderLen = 32 + // [ChainID (Bytes32)] + - 32 + // [BodyMR (Bytes32)] + - 32 + // [PrevKeyMR (Bytes32)] + - 32 + // [PrevFullHash (Bytes32)] + - 4 + // [EB Sequence (uint32 BE)] + - 4 + // [DB Height (uint32 BE)] + - 4 // [Entry Count (uint32 BE)] - - EBlockObjectLen = 32 // Entry hash or minute marker - - EBlockMinBodyLen = EBlockObjectLen * 2 // one entry hash & one minute marker - EBlockMinTotalLen = EBlockHeaderLen + EBlockMinBodyLen - - EBlockMaxBodyLen = math.MaxUint32 * EBlockObjectLen - EBlockMaxTotalLen = EBlockHeaderLen + EBlockMaxBodyLen -) - -// UnmarshalBinary unmarshals raw entry block data. -// -// Header -// -// [ChainID (Bytes32)] + -// [BodyMR (Bytes32)] + -// [PrevKeyMR (Bytes32)] + -// [PrevFullHash (Bytes32)] + -// [EB Sequence (uint32 BE)] + -// [DB Height (uint32 BE)] + -// [Object Count (uint32 BE)] -// -// Body -// -// [Object 0 (Bytes32)] // entry hash or minute marker + -// ... + -// [Object N (Bytes32)] -// -// https://github.com/FactomProject/FactomDocs/blob/master/factomDataStructureDetails.md#entry-block -func (eb *EBlock) UnmarshalBinary(data []byte) error { - if len(data) < EBlockMinTotalLen { - return fmt.Errorf("insufficient length") - } - if len(data) > EBlockMaxTotalLen { - return fmt.Errorf("invalid length") - } - - eb.ChainID = new(Bytes32) - i := copy(eb.ChainID[:], data[:len(eb.ChainID)]) - eb.BodyMR = new(Bytes32) - i += copy(eb.BodyMR[:], data[i:i+len(eb.BodyMR)]) - eb.PrevKeyMR = new(Bytes32) - i += copy(eb.PrevKeyMR[:], data[i:i+len(eb.PrevKeyMR)]) - eb.PrevFullHash = new(Bytes32) - i += copy(eb.PrevFullHash[:], data[i:i+len(eb.PrevFullHash)]) - eb.Sequence = binary.BigEndian.Uint32(data[i : i+4]) - i += 4 - eb.Height = binary.BigEndian.Uint32(data[i : i+4]) - i += 4 - eb.ObjectCount = binary.BigEndian.Uint32(data[i : i+4]) - i += 4 - if len(data[i:]) != int(eb.ObjectCount*32) { - return fmt.Errorf("invalid length") - } - - // Parse all objects into Bytes32 - objects := make([]Bytes32, eb.ObjectCount) - maxMinute := Bytes32{31: 10} - var numMins int - for oi := range objects { - obj := &objects[len(objects)-1-oi] // Reverse object order - i += copy(obj[:], data[i:i+len(obj)]) - if bytes.Compare(obj[:], maxMinute[:]) <= 0 { // if obj <= maxMinute - numMins++ // obj is a minute marker - } - } - // The last object (which is now index 0) must be a minute marker. - if bytes.Compare(objects[0][:], maxMinute[:]) > 0 { // if obj > maxMinute - return fmt.Errorf("invalid minute marker") - } - - // Populate Entries from objects. - eb.Entries = make([]Entry, int(eb.ObjectCount)-numMins) - ei := len(eb.Entries) - 1 - var ts time.Time - for _, obj := range objects { - if bytes.Compare(obj[:], maxMinute[:]) <= 0 { - ts = eb.Timestamp. - Add(time.Duration(obj[31]) * time.Minute) - continue - } - e := &eb.Entries[ei] - e.Timestamp = ts - e.ChainID = eb.ChainID - obj := obj - e.Hash = &obj - ei-- - } - return nil -} - -func (eb *EBlock) MarshalBinaryHeader() ([]byte, error) { - data := make([]byte, eb.MarshalBinaryLen()) - i := copy(data, eb.ChainID[:]) - i += copy(data[i:], eb.BodyMR[:]) - i += copy(data[i:], eb.PrevKeyMR[:]) - i += copy(data[i:], eb.PrevFullHash[:]) - binary.BigEndian.PutUint32(data[i:], eb.Sequence) - i += 4 - binary.BigEndian.PutUint32(data[i:], eb.Height) - i += 4 - eb.ObjectCount = eb.CountObjects() - binary.BigEndian.PutUint32(data[i:], eb.ObjectCount) - i += 4 - return data, nil -} - -func (eb *EBlock) MarshalBinary() ([]byte, error) { - data, err := eb.MarshalBinaryHeader() - if err != nil { - return nil, err - } - objects, err := eb.Objects() - if err != nil { - return nil, err - } - i := EBlockHeaderLen - for _, obj := range objects { - i += copy(data[i:], obj[:]) - } - return data, nil -} - -func (eb *EBlock) Objects() ([]Bytes32, error) { - if eb.ObjectCount == 0 { - eb.ObjectCount = eb.CountObjects() - } - objects := make([]Bytes32, eb.ObjectCount) - var lastMin, oi int - lastMin = int(eb.Entries[0].Timestamp.Sub(eb.Timestamp).Minutes()) - for _, e := range eb.Entries { - min := int(e.Timestamp.Sub(eb.Timestamp).Minutes()) - if min > 10 { - return nil, fmt.Errorf("invalid entry timestamp") - } - if min > lastMin { - objects[oi][31] = byte(lastMin) - oi++ - lastMin = min - } - objects[oi] = *e.Hash - oi++ - } - // Insert last minute marker - lastE := eb.Entries[len(eb.Entries)-1] - lastMin = int(lastE.Timestamp.Sub(eb.Timestamp).Minutes()) - objects[oi][31] = byte(lastMin) - return objects, nil -} - -func (eb EBlock) CountObjects() uint32 { - if len(eb.Entries) == 0 { - panic("no entries") - } - mins := make([]bool, 10) - var numMins int - for _, e := range eb.Entries { - minute := e.Timestamp.Sub(eb.Timestamp) - // Except for zero, timestamps get rounded down including those - // that fall exactly on a minute. - if minute > 0 && minute.Truncate(time.Minute) == minute { - minute -= time.Minute - } else { - minute = minute.Truncate(time.Minute) - } - mi := int(minute.Minutes()) - if !mins[mi] { - mins[mi] = true - numMins++ - } - } - return uint32(len(eb.Entries) + numMins) -} - -func (eb *EBlock) MarshalBinaryLen() int { - if eb.ObjectCount == 0 { - eb.ObjectCount = eb.CountObjects() - } - return EBlockHeaderLen + int(eb.ObjectCount)*len(Bytes32{}) -} - -func (eb *EBlock) ComputeBodyMR() (Bytes32, error) { - var bodyMR Bytes32 - objects, err := eb.Objects() - if err != nil { - return Bytes32{}, err - } - blocks := make([][]byte, len(objects)) - for i := range objects { - blocks[i] = objects[i][:] - } - tree := merkle.NewTreeWithOpts(merkle.TreeOptions{ - DoubleOddNodes: true, - DisableHashLeaves: true}) - if err := tree.Generate(blocks, sha256.New()); err != nil { - return Bytes32{}, err - } - root := tree.Root() - copy(bodyMR[:], root.Hash) - return bodyMR, nil -} - -func (eb *EBlock) ComputeFullHash() (Bytes32, error) { - data, err := eb.MarshalBinary() - if err != nil { - return Bytes32{}, err - } - return sha256.Sum256(data), nil -} - -func (eb *EBlock) ComputeHeaderHash() (Bytes32, error) { - header, err := eb.MarshalBinaryHeader() - if err != nil { - return Bytes32{}, err - } - return sha256.Sum256(header[:EBlockHeaderLen]), nil -} - -func (eb *EBlock) ComputeKeyMR() (Bytes32, error) { - headerHash, err := eb.ComputeHeaderHash() - if err != nil { - return Bytes32{}, err - } - bodyMR, err := eb.ComputeBodyMR() - if err != nil { - return Bytes32{}, err - } - data := make([]byte, len(headerHash)+len(bodyMR)) - i := copy(data, headerHash[:]) - copy(data[i:], bodyMR[:]) - return sha256.Sum256(data), nil -} - -func (eb *EBlock) SetTimestamp(ts time.Time) { - prevTs := eb.Timestamp - for i := range eb.Entries { - e := &eb.Entries[i] - e.Timestamp = ts.Add(e.Timestamp.Sub(prevTs)) - } - eb.Timestamp = ts -} diff --git a/factom/entry.go b/factom/entry.go deleted file mode 100644 index 38e5caf..0000000 --- a/factom/entry.go +++ /dev/null @@ -1,460 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "bytes" - "crypto/sha256" - "crypto/sha512" - "encoding/binary" - "encoding/json" - "fmt" - "math/rand" - "time" - - "golang.org/x/crypto/ed25519" -) - -// ChainID returns the chain ID for a set of NameIDs. -func ChainID(nameIDs []Bytes) Bytes32 { - hash := sha256.New() - for _, id := range nameIDs { - idSum := sha256.Sum256(id) - hash.Write(idSum[:]) - } - c := hash.Sum(nil) - var chainID Bytes32 - copy(chainID[:], c) - return chainID -} - -// Entry represents a Factom Entry with some additional useful fields. Both -// Timestamp and Height are not included in the entry binary structure or hash. -// These fields will only be populated if this Entry was initially part of a -// populated EBlock, and can be manipulated in this type without affecting the -// Entry Hash. -type Entry struct { - // EBlock.Get populates the Hash, Timestamp, ChainID, and Height. - Hash *Bytes32 `json:"entryhash,omitempty"` - Timestamp time.Time `json:"-"` - ChainID *Bytes32 `json:"chainid,omitempty"` - - // Entry.Get populates the Content and ExtIDs. - ExtIDs []Bytes `json:"extids"` - Content Bytes `json:"content"` -} - -// IsPopulated returns true if e has already been successfully populated by a -// call to Get. IsPopulated returns false if e.ExtIDs, e.Content, or e.Hash are -// nil, or if e.Timestamp is zero. -func (e Entry) IsPopulated() bool { - return e.ExtIDs != nil && - e.Content != nil && - e.ChainID != nil && - e.Hash != nil -} - -// Get queries factomd for the entry corresponding to e.Hash, which must be not -// nil. After a successful call e.Content, e.ExtIDs, and e.Timestamp will be -// populated. -func (e *Entry) Get(c *Client) error { - // If the Hash is nil then we have nothing to query for. - if e.Hash == nil { - return fmt.Errorf("Hash is nil") - } - if e.ChainID == nil { - return fmt.Errorf("ChainID is nil") - } - // If the Entry is already populated then there is nothing to do. If - // the Hash is nil, we cannot populate it anyway. - if e.IsPopulated() { - return nil - } - params := struct { - Hash *Bytes32 `json:"hash"` - }{Hash: e.Hash} - var result struct { - Data Bytes `json:"data"` - } - if err := c.FactomdRequest("raw-data", params, &result); err != nil { - return err - } - if EntryHash(result.Data) != *e.Hash { - return fmt.Errorf("invalid hash") - } - return e.UnmarshalBinary(result.Data) -} - -type chainFirstEntryParams struct { - Entry *Entry `json:"firstentry"` -} -type composeChainParams struct { - Chain chainFirstEntryParams `json:"chain"` - EC ECAddress `json:"ecpub"` -} -type composeEntryParams struct { - Entry *Entry `json:"entry"` - EC ECAddress `json:"ecpub"` -} - -type composeJRPC struct { - Method string `json:"method"` - Params json.RawMessage `json:"params"` -} -type composeResult struct { - Commit composeJRPC `json:"commit"` - Reveal composeJRPC `json:"reveal"` -} -type commitResult struct { - TxID *Bytes32 -} - -// Create queries factom-walletd to compose and factomd to commit and reveal a -// new Entry or new Chain, if e.ChainID is nil. ec must exist in -// factom-walletd's keystore. -func (e *Entry) Create(c *Client, ec ECAddress) (*Bytes32, error) { - var params interface{} - var method string - if e.ChainID == nil { - method = "compose-chain" - params = composeChainParams{ - Chain: chainFirstEntryParams{Entry: e}, - EC: ec, - } - } else { - method = "compose-entry" - params = composeEntryParams{Entry: e, EC: ec} - } - result := composeResult{} - if err := c.WalletdRequest(method, params, &result); err != nil { - return nil, err - } - if len(result.Commit.Method) == 0 { - return nil, fmt.Errorf("Wallet request error: method: %#v", method) - } - - var commit commitResult - if err := c.FactomdRequest(result.Commit.Method, result.Commit.Params, - &commit); err != nil { - return nil, err - } - if err := c.FactomdRequest(result.Reveal.Method, result.Reveal.Params, - e); err != nil { - return nil, err - } - return commit.TxID, nil -} - -// ComposeCreate Composes e locally and then Commit and Reveals it using -// factomd. This does not make any calls to factom-walletd. The Transaction ID -// is returned. -func (e *Entry) ComposeCreate(c *Client, es EsAddress) (*Bytes32, error) { - var commit, reveal []byte - commit, reveal, txID, err := e.Compose(es) - if err != nil { - return nil, err - } - if err := c.Commit(commit); err != nil { - return txID, err - } - if err := c.Reveal(reveal); err != nil { - return txID, err - } - return txID, nil -} - -// Commit sends an entry or new chain commit to factomd. -func (c *Client) Commit(commit []byte) error { - var method string - switch len(commit) { - case commitLen: - method = "commit-entry" - case chainCommitLen: - method = "commit-chain" - default: - return fmt.Errorf("invalid length") - } - - params := struct { - Commit Bytes `json:"message"` - }{Commit: commit} - if err := c.FactomdRequest(method, params, nil); err != nil { - return err - } - return nil -} - -// Reveal reveals an entry or new chain entry to factomd. -func (c *Client) Reveal(reveal []byte) error { - params := struct { - Reveal Bytes `json:"entry"` - }{Reveal: reveal} - if err := c.FactomdRequest("reveal-entry", params, nil); err != nil { - return err - } - return nil -} - -const ( - commitLen = 1 + // version - 6 + // timestamp - 32 + // entry hash - 1 + // ec cost - 32 + // ec pub - 64 // sig - chainCommitLen = 1 + // version - 6 + // timestamp - 32 + // chain id hash - 32 + // commit weld - 32 + // entry hash - 1 + // ec cost - 32 + // ec pub - 64 // sig -) - -// Compose generates the commit and reveal data required to submit an entry to -// factomd. If e.ChainID is nil, then the ChainID is computed from the e.ExtIDs -// and a new chain commit is created. -func (e *Entry) Compose(es EsAddress) (commit []byte, reveal []byte, txID *Bytes32, - err error) { - var newChain bool - if e.ChainID == nil { - newChain = true - } - reveal, err = e.MarshalBinary() // Populates ChainID and Hash - if err != nil { - return - } - - size := commitLen - if newChain { - size = chainCommitLen - } - commit = make([]byte, size) - - // Timestamp - ms := time.Now(). - Add(time.Duration(-rand.Int63n(int64(1*time.Hour)))). - UnixNano() / 1e6 - buf := bytes.NewBuffer(make([]byte, 0, 8)) - binary.Write(buf, binary.BigEndian, ms) - i := 1 // Skip version byte - i += copy(commit[i:], buf.Bytes()[2:]) - - if newChain { - // ChainID Hash - chainIDHash := sha256d(e.ChainID[:]) - i += copy(commit[i:], chainIDHash[:]) - - // Commit Weld sha256d(entryhash | chainid) - weld := sha256d(append(e.Hash[:], e.ChainID[:]...)) - i += copy(commit[i:], weld[:]) - } - - // Entry Hash - i += copy(commit[i:], e.Hash[:]) - - // Cost - cost, _ := EntryCost(len(reveal)) - if newChain { - cost += NewChainCost - } - commit[i] = byte(cost) - i++ - signedDataEndIndex := i - txID = new(Bytes32) - *txID = sha256.Sum256(commit[:i]) - - // Public Key - i += copy(commit[i:], es.PublicKey()) - - // Signature - sig := ed25519.Sign(es.PrivateKey(), commit[:signedDataEndIndex]) - copy(commit[i:], sig) - - return -} - -// NewChainCost is the fixed added cost of creating a new chain. -const NewChainCost = 10 - -// EntryCost returns the required Entry Credit cost for an entry with encoded -// length equal to size. An error is returned if size exceeds 10275. -func EntryCost(size int) (int8, error) { - size -= EntryHeaderLen - if size > 10240 { - return 0, fmt.Errorf("Entry cannot be larger than 10KB") - } - cost := int8(size / 1024) - if size%1024 > 0 { - cost++ - } - if cost < 1 { - cost = 1 - } - return cost, nil -} - -func (e Entry) Cost() (int8, error) { - cost, err := EntryCost(e.MarshalBinaryLen()) - if err != nil { - return 0, err - } - if e.ChainID == nil { - cost += NewChainCost - } - return cost, nil -} - -func (e Entry) MarshalBinaryLen() int { - extIDTotalLen := len(e.ExtIDs) * 2 // Two byte len(ExtID) per ExtID - for _, extID := range e.ExtIDs { - extIDTotalLen += len(extID) - } - return extIDTotalLen + len(e.Content) + EntryHeaderLen -} - -// MarshalBinary marshals the entry to its binary representation. See -// UnmarshalBinary for encoding details. MarshalBinary populates e.ChainID if -// nil, and always overwrites e.Hash with the computed EntryHash. This is also -// the reveal data. -func (e *Entry) MarshalBinary() ([]byte, error) { - totalLen := e.MarshalBinaryLen() - if totalLen > EntryMaxTotalLen { - return nil, fmt.Errorf("Entry cannot be larger than 10KB") - } - if e.ChainID == nil { - // We assume this is a chain creation entry. - e.ChainID = new(Bytes32) - *e.ChainID = ChainID(e.ExtIDs) - } - // Header, version byte 0x00 - data := make([]byte, totalLen) - i := 1 - i += copy(data[i:], e.ChainID[:]) - binary.BigEndian.PutUint16(data[i:i+2], - uint16(totalLen-len(e.Content)-EntryHeaderLen)) - i += 2 - - // Payload - for _, extID := range e.ExtIDs { - n := len(extID) - binary.BigEndian.PutUint16(data[i:i+2], uint16(n)) - i += 2 - i += copy(data[i:], extID) - } - copy(data[i:], e.Content) - // Compute and save entry hash for later use - if e.Hash == nil { - e.Hash = new(Bytes32) - } - if e.Hash.IsZero() { - *e.Hash = EntryHash(data) - } - return data, nil -} - -const ( - EntryHeaderLen = 1 + // version - 32 + // chain id - 2 // total len - - EntryMaxDataLen = 10240 - EntryMaxTotalLen = EntryMaxDataLen + EntryHeaderLen -) - -// UnmarshalBinary unmarshals raw entry data. It does not populate the -// Entry.Hash. Entries are encoded as follows: -// -// [Version byte (0x00)] + -// [ChainID (Bytes32)] + -// [Total ExtID encoded length (uint16 BE)] + -// [ExtID 0 length (uint16)] + [ExtID 0 (Bytes)] + -// ... + -// [ExtID X length (uint16)] + [ExtID X (Bytes)] + -// [Content (Bytes)] -// -// https://github.com/FactomProject/FactomDocs/blob/master/factomDataStructureDetails.md#entry -func (e *Entry) UnmarshalBinary(data []byte) error { - if len(data) < EntryHeaderLen { - return fmt.Errorf("insufficient length") - } - if len(data) > EntryMaxTotalLen { - return fmt.Errorf("invalid length") - } - if data[0] != 0x00 { - return fmt.Errorf("invalid version byte") - } - i := 1 - e.ChainID = new(Bytes32) - i += copy(e.ChainID[:], data[i:i+len(e.ChainID)]) - extIDTotalLen := int(binary.BigEndian.Uint16(data[33:35])) - if extIDTotalLen == 1 || EntryHeaderLen+extIDTotalLen > len(data) { - return fmt.Errorf("invalid ExtIDs length") - } - - e.ExtIDs = []Bytes{} - pos := EntryHeaderLen - for pos < EntryHeaderLen+extIDTotalLen { - extIDLen := int(binary.BigEndian.Uint16(data[pos : pos+2])) - if pos+2+extIDLen > EntryHeaderLen+extIDTotalLen { - return fmt.Errorf("error parsing ExtIDs") - } - pos += 2 - e.ExtIDs = append(e.ExtIDs, Bytes(data[pos:pos+extIDLen])) - pos += extIDLen - } - - e.Content = data[pos:] - - // Compute and save entry hash if not populated. - if e.Hash == nil { - e.Hash = new(Bytes32) - } - if e.Hash.IsZero() { - *e.Hash = EntryHash(data) - } - return nil -} - -// ComputeHash returns the Entry's hash as computed by hashing the binary -// representation of the Entry. -func (e *Entry) ComputeHash() (Bytes32, error) { - data, err := e.MarshalBinary() - if err != nil { - return Bytes32{}, err - } - hash := EntryHash(data) - e.Hash = &hash - return hash, err -} - -// EntryHash returns the Entry hash of data. Entry's are hashed via: -// sha256(sha512(data) + data). -func EntryHash(data []byte) Bytes32 { - sum := sha512.Sum512(data) - saltedSum := make([]byte, len(sum)+len(data)) - i := copy(saltedSum, sum[:]) - copy(saltedSum[i:], data) - return sha256.Sum256(saltedSum) -} diff --git a/factom/entry_test.go b/factom/entry_test.go deleted file mode 100644 index b01077f..0000000 --- a/factom/entry_test.go +++ /dev/null @@ -1,228 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom_test - -import ( - "encoding/hex" - "fmt" - "testing" - - "github.com/AdamSLevy/jsonrpc2/v11" - . "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var marshalBinaryTests = []struct { - Name string - Hash *Bytes32 - Entry -}{{ - Name: "valid", - Entry: Entry{ - Hash: NewBytes32(hexToBytes( - "72177d733dcd0492066b79c5f3e417aef7f22909674f7dc351ca13b04742bb91")), - ChainID: func() *Bytes32 { c := ChainID([]Bytes{Bytes("test")}); return &c }(), - Content: hexToBytes("5061796c6f616448657265"), - }, -}} - -func TestEntryMarshalBinary(t *testing.T) { - for _, test := range marshalBinaryTests { - t.Run(test.Name, func(t *testing.T) { - e := test.Entry - hash, err := e.ComputeHash() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(*e.Hash, hash) - }) - } -} - -var unmarshalBinaryTests = []struct { - Name string - Data []byte - Error string - Hash *Bytes32 -}{{ - Name: "valid", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc20757700530009436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002c4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Hash: NewBytes32(hexToBytes( - "a5e49c1c14762f067b4132c5aa3abf03efdf2569de5d68a3f7cd539577f54942")), -}, { - Name: "invalid (too short)", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc207577"), - Error: "insufficient length", -}, { - Name: "invalid (version byte)", - Data: hexToBytes( - "019005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc20757700530009436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002c4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Error: "invalid version byte", -}, { - Name: "invalid (ext ID Total Len)", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc20757700010009436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002c4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Error: "invalid ExtIDs length", -}, { - Name: "invalid (ext ID Total Len)", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc207577ffff0009436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002c4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Error: "invalid ExtIDs length", -}, { - Name: "invalid (ext ID len)", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc20757700530008436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002c4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Error: "error parsing ExtIDs", -}, { - Name: "invalid (ext ID len)", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc2075770053000a436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002c4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Error: "error parsing ExtIDs", -}, { - Name: "invalid (ext ID len)", - Data: hexToBytes( - "009005bb7dd69fb9910ee0b0db7b8a01198f03623eab6dadf1eba01f9dbc20757700530009436861696e54797065001253494e474c455f50524f4f465f434841494e000448617368002b4a74446f413157476a784f63584a67496365574e6336396a5551524867506835414e337848646b6a7158303d48796742426b32317a79384c576e5a56785a48526c38706b502f366e34377546317664324a4378654238593d"), - Error: "error parsing ExtIDs", -}} - -func TestEntry(t *testing.T) { - for _, test := range unmarshalBinaryTests { - t.Run("UnmarshalBinary/"+test.Name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - e := Entry{} - err := e.UnmarshalBinary(test.Data) - if len(test.Error) == 0 { - require.NoError(err) - require.NotNil(e.ChainID) - hash, err := e.ComputeHash() - assert.NoError(err) - assert.Equal(*test.Hash, hash) - } else { - require.EqualError(err, test.Error) - } - }) - } - - var ecAddressStr = "EC1zANmWuEMYoH6VizJg6uFaEdi8Excn1VbLN99KRuxh3GSvB7YQ" - ec, _ := NewECAddress(ecAddressStr) - chainID := ChainID([]Bytes{Bytes(ec[:])}) - t.Run("ComposeCreate", func(t *testing.T) { - c := NewClient() - es, err := ec.GetEsAddress(c) - if _, ok := err.(jsonrpc2.Error); ok { - // Skip if the EC address is not in the wallet. - t.SkipNow() - } - assert := assert.New(t) - assert.NoError(err) - balance, err := ec.GetBalance(c) - assert.NoError(err) - if balance == 0 { - // Skip if the EC address is not funded. - t.SkipNow() - } - - randData, err := GenerateEsAddress() - assert.NoError(err) - e := Entry{Content: Bytes(randData[:]), - ExtIDs: []Bytes{Bytes(ec[:])}, - ChainID: &chainID} - tx, err := e.ComposeCreate(c, es) - assert.NoError(err) - assert.NotNil(tx) - fmt.Println("Tx: ", tx) - fmt.Println("Entry Hash: ", e.Hash) - fmt.Println("Chain ID: ", e.ChainID) - - e.ChainID = nil - e.Content = Bytes(randData[:]) - e.ExtIDs = []Bytes{Bytes(randData[:])} - tx, err = e.ComposeCreate(c, es) - assert.NoError(err) - assert.NotNil(tx) - fmt.Println("Tx: ", tx) - fmt.Println("Entry Hash: ", e.Hash) - fmt.Println("Chain ID: ", e.ChainID) - }) - t.Run("Create", func(t *testing.T) { - c := NewClient() - c.Factomd.DebugRequest = true - c.Walletd.DebugRequest = true - balance, err := ec.GetBalance(c) - assert := assert.New(t) - require := require.New(t) - require.NoError(err) - if balance == 0 { - // Skip if the EC address is not funded. - t.SkipNow() - } - - randData, err := GenerateEsAddress() - assert.NoError(err) - e := Entry{Content: Bytes(randData[:]), - ExtIDs: []Bytes{Bytes(ec[:])}, - ChainID: &chainID} - tx, err := e.Create(c, ec) - assert.NoError(err) - assert.NotNil(tx) - fmt.Println("Tx: ", tx) - fmt.Println("Entry Hash: ", e.Hash) - fmt.Println("Chain ID: ", e.ChainID) - - e.ChainID = nil - e.Content = Bytes(randData[:]) - e.ExtIDs = []Bytes{Bytes(randData[:])} - tx, err = e.Create(c, ec) - assert.NoError(err) - assert.NotNil(tx) - fmt.Println("Tx: ", tx) - fmt.Println("Entry Hash: ", e.Hash) - fmt.Println("Chain ID: ", e.ChainID) - }) - t.Run("Compose/too large", func(t *testing.T) { - assert := assert.New(t) - e := Entry{Content: make(Bytes, 11000), - ExtIDs: []Bytes{Bytes(ec[:])}, - ChainID: &chainID} - _, _, _, err := e.Compose(EsAddress(ec)) - assert.EqualError(err, "Entry cannot be larger than 10KB") - }) - t.Run("EntryCost", func(t *testing.T) { - assert := assert.New(t) - _, err := EntryCost(11000) - assert.EqualError(err, "Entry cannot be larger than 10KB") - cost, _ := EntryCost(0) - assert.Equal(int8(1), cost) - }) -} - -func hexToBytes(hexStr string) Bytes { - raw, err := hex.DecodeString(hexStr) - if err != nil { - panic(err) - } - return Bytes(raw) -} diff --git a/factom/gen.go b/factom/gen.go deleted file mode 100644 index a34a7ec..0000000 --- a/factom/gen.go +++ /dev/null @@ -1,27 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -//go:generate go run ./genmain.go -//go:generate goimports -w ./idkey_gen.go ./idkey_gen_test.go -//go:generate gofmt -s -w ./idkey_gen.go ./idkey_gen_test.go diff --git a/factom/genmain.go b/factom/genmain.go deleted file mode 100644 index 93da74f..0000000 --- a/factom/genmain.go +++ /dev/null @@ -1,92 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// +build ignore - -package main - -import ( - "log" - "os" - . "text/template" -) - -var idKeys = []struct { - ID int - IDPrefix string - SKPrefix string - IDStr string - SKStr string -}{{ - ID: 1, - IDPrefix: "0x3f, 0xbe, 0xba", - SKPrefix: "0x4d, 0xb6, 0xc9", - IDStr: "id12K4tCXKcJJYxJmZ1UY9EuKPvtGVAjo32xySMKNUahbmRcsqFgW", - SKStr: "sk13iLKJfxNQg8vpSmjacEgEQAnXkn7rbjd5ewexc1Un5wVPa7KTk", -}, { - ID: 2, - IDPrefix: "0x3f, 0xbe, 0xd8", - SKPrefix: "0x4d, 0xb6, 0xe7", - IDStr: "id22pNvsaMWf9qxWFrmfQpwFJiKQoWfKmBwVgQtdvqVZuqzGmrFNY", - SKStr: "sk22UaDys2Mzg2pUCsToo9aKgxubJFnZN5Bc2LXfV59VxMvXXKwXa", -}, { - ID: 3, - IDPrefix: "0x3f, 0xbe, 0xf6", - SKPrefix: "0x4d, 0xb7, 0x05", - IDStr: "id33pRgpm8ufXNGxtW7n5FgdGP6afXKjU4LfVmgfC8Yaq6LyYq2wA", - SKStr: "sk32Xyo9kmjtNqRUfRd3ZhU56NZd8M1nR61tdBaCLSQRdhUCk4yiM", -}, { - ID: 4, - IDPrefix: "0x3f, 0xbf, 0x14", - SKPrefix: "0x4d, 0xb7, 0x23", - IDStr: "id42vYqBB63eoSz8DHozEwtCaLbEwvBTG9pWgD3D5CCaHWy1gCjF5", - SKStr: "sk43eMusQuvvChoGNn1VZZwbAH8BtKJSZNC7ZWoz1Vc4Y3greLA45", -}} - -func main() { - idKeyGoTmplt := Must(ParseFiles("./idkey.tmpl")) - idKeyTestGoTmplt := Must(ParseFiles("./idkey_test.tmpl")) - - idKeyGoFile, err := os.OpenFile("./idkey_gen.go", - os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - log.Fatal(err) - } - defer idKeyGoFile.Close() - - idKeyTestGoFile, err := os.OpenFile("./idkey_gen_test.go", - os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - log.Fatal(err) - } - defer idKeyTestGoFile.Close() - - err = idKeyGoTmplt.Execute(idKeyGoFile, idKeys) - if err != nil { - log.Fatal(err) - } - - err = idKeyTestGoTmplt.Execute(idKeyTestGoFile, idKeys) - if err != nil { - log.Fatal(err) - } -} diff --git a/factom/heights.go b/factom/heights.go deleted file mode 100644 index 7ed5797..0000000 --- a/factom/heights.go +++ /dev/null @@ -1,54 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -// Heights contains all of the distinct heights for a factomd node and the -// Factom network. -type Heights struct { - // The current directory block height of the local factomd node. - DirectoryBlock uint32 `json:"directoryblockheight"` - - // The current block being worked on by the leaders in the network. - // This block is not yet complete, but all transactions submitted will - // go into this block (depending on network conditions, the transaction - // may be delayed into the next block) - Leader uint32 `json:"leaderheight"` - - // The height at which the factomd node has all the entry blocks. - // Directory blocks are obtained first, entry blocks could be lagging - // behind the directory block when syncing. - EntryBlock uint32 `json:"entryblockheight"` - - // The height at which the local factomd node has all the entries. If - // you added entries at a block height above this, they will not be - // able to be retrieved by the local factomd until it syncs further. - Entry uint32 `json:"entryheight"` -} - -// Get uses c to call the "heights" RPC method and populates h with the result. -func (h *Heights) Get(c *Client) error { - if err := c.FactomdRequest("heights", nil, &h); err != nil { - return err - } - return nil -} diff --git a/factom/heights_test.go b/factom/heights_test.go deleted file mode 100644 index a7f71e3..0000000 --- a/factom/heights_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestHeights(t *testing.T) { - var h Heights - assert := assert.New(t) - c := NewClient() - err := h.Get(c) - assert.NoError(err) - zero := uint32(0) - assert.NotEqual(zero, h.DirectoryBlock) - assert.NotEqual(zero, h.Leader) - assert.NotEqual(zero, h.EntryBlock) - assert.NotEqual(zero, h.Entry) -} diff --git a/factom/identity.go b/factom/identity.go deleted file mode 100644 index 46a58b3..0000000 --- a/factom/identity.go +++ /dev/null @@ -1,129 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import "fmt" - -// ValidIdentityChainID returns true if the chainID matches the pattern for an -// Identity Chain ID. -// -// The Identity Chain specification can be found here: -// https://github.com/FactomProject/FactomDocs/blob/master/Identity.md#factom-identity-chain-creation -func ValidIdentityChainID(chainID Bytes) bool { - if len(chainID) == len(Bytes32{}) && - chainID[0] == 0x88 && - chainID[1] == 0x88 && - chainID[2] == 0x88 { - return true - } - return false -} - -// ValidIdentityNameIDs returns true if the nameIDs match the pattern for a -// valid Identity Chain. The nameIDs for a chain are the ExtIDs of the first -// entry in the chain. -// -// The Identity Chain specification can be found here: -// https://github.com/FactomProject/FactomDocs/blob/master/Identity.md#factom-identity-chain-creation -func ValidIdentityNameIDs(nameIDs []Bytes) bool { - if len(nameIDs) == 7 && - len(nameIDs[0]) == 1 && nameIDs[0][0] == 0x00 && - string(nameIDs[1]) == "Identity Chain" && - len(nameIDs[2]) == len(ID1Key{}) && - len(nameIDs[3]) == len(ID2Key{}) && - len(nameIDs[4]) == len(ID3Key{}) && - len(nameIDs[5]) == len(ID4Key{}) { - return true - } - return false -} - -// Identity represents the Token Issuer's Identity Chain and the public ID1Key. -type Identity struct { - ID1 ID1Key - Height uint32 - Entry -} - -// NewIdentity initializes an Identity with the given chainID. -func NewIdentity(chainID *Bytes32) (i Identity) { - i.ChainID = chainID - return -} - -// IsPopulated returns true if the Identity has been populated with an ID1Key. -func (i Identity) IsPopulated() bool { - return !Bytes32(i.ID1).IsZero() -} - -// Get validates i.ChainID as an Identity Chain and parses out the ID1Key. -func (i *Identity) Get(c *Client) error { - if i.ChainID == nil { - return fmt.Errorf("ChainID is nil") - } - if i.IsPopulated() { - return nil - } - if !ValidIdentityChainID(i.ChainID[:]) { - return nil - } - - // Get first entry block of Identity Chain. - eb := EBlock{ChainID: i.ChainID} - if err := eb.GetFirst(c); err != nil { - return err - } - - // Get first entry of first entry block. - first := eb.Entries[0] - if err := first.Get(c); err != nil { - return err - } - - if !ValidIdentityNameIDs(first.ExtIDs) { - return nil - } - - i.Height = eb.Height - i.Entry = first - copy(i.ID1[:], first.ExtIDs[2]) - - return nil -} - -func (i *Identity) UnmarshalBinary(data []byte) error { - if err := i.Entry.UnmarshalBinary(data); err != nil { - return err - } - if !ValidIdentityChainID(i.ChainID[:]) { - return fmt.Errorf("invalid identity ChainID format") - } - if !ValidIdentityNameIDs(i.ExtIDs) { - return fmt.Errorf("invalid identity NameID format") - } - if *i.ChainID != ChainID(i.ExtIDs) { - return fmt.Errorf("invalid ExtIDs: Chain ID mismatch") - } - copy(i.ID1[:], i.ExtIDs[2]) - return nil -} diff --git a/factom/identity_test.go b/factom/identity_test.go deleted file mode 100644 index 9854c87..0000000 --- a/factom/identity_test.go +++ /dev/null @@ -1,242 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "encoding/hex" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var validIdentityChainIDStr = "88888807e4f3bbb9a2b229645ab6d2f184224190f83e78761674c2362aca4425" - -func validIdentityChainID() Bytes { - return hexToBytes(validIdentityChainIDStr) -} - -func hexToBytes(hexStr string) Bytes { - raw, err := hex.DecodeString(hexStr) - if err != nil { - panic(err) - } - return Bytes(raw) -} - -func newID1Key(b []byte) *ID1Key { - key := new(ID1Key) - copy(key[:], b) - return key -} - -var validIdentityChainIDTests = []struct { - Name string - Valid bool - ChainID Bytes -}{{ - Name: "valid", - ChainID: validIdentityChainID(), - Valid: true, -}, { - Name: "nil", - ChainID: nil, -}, { - Name: "invalid length (short)", - ChainID: validIdentityChainID()[0:15], -}, { - Name: "invalid length (long)", - ChainID: append(validIdentityChainID(), 0x00), -}, { - Name: "invalid header", - ChainID: func() Bytes { c := validIdentityChainID(); c[0]++; return c }(), -}, { - Name: "invalid header", - ChainID: func() Bytes { c := validIdentityChainID(); c[1]++; return c }(), -}, { - Name: "invalid header", - ChainID: func() Bytes { c := validIdentityChainID(); c[2]++; return c }(), -}} - -func TestValidIdentityChainID(t *testing.T) { - for _, test := range validIdentityChainIDTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - valid := ValidIdentityChainID(test.ChainID) - if test.Valid { - assert.True(valid) - } else { - assert.False(valid) - } - }) - } -} - -func validIdentityNameIDs() []Bytes { - return []Bytes{ - Bytes{0x00}, - Bytes("Identity Chain"), - hexToBytes("f825c5629772afb5bce0464e5ea1af244be853a692d16360b8e03d6164b6adb5"), - hexToBytes("28baa7d04e6c102991a184533b9f2443c9c314cc0327cc3a2f2adc0f3d7373a1"), - hexToBytes("6095733cf6f5d0b5411d1eeb9f6699fad1ae27f9d4da64583bef97008d7bf0c9"), - hexToBytes("966ebc2a0e3877ed846167e95ba3dde8561d90ee9eddd1bb74fbd6d1d25dba0f"), - hexToBytes("33363533323533"), - } -} - -func invalidIdentityNameIDs(i int) []Bytes { - n := validIdentityNameIDs() - n[i] = Bytes{} - return n -} - -var validIdentityNameIDsTests = []struct { - Name string - Valid bool - NameIDs []Bytes -}{{ - Name: "valid", - NameIDs: validIdentityNameIDs(), - Valid: true, -}, { - Name: "nil", - NameIDs: nil, -}, { - Name: "invalid length (short)", - NameIDs: validIdentityNameIDs()[0:6], -}, { - Name: "invalid length (long)", - NameIDs: append(validIdentityNameIDs(), Bytes{}), -}, { - Name: "invalid length (long)", - NameIDs: append(validIdentityNameIDs(), Bytes{}), -}, { - Name: "invalid ExtID", - NameIDs: invalidIdentityNameIDs(0), -}, { - Name: "invalid ExtID", - NameIDs: invalidIdentityNameIDs(1), -}, { - Name: "invalid ExtID", - NameIDs: invalidIdentityNameIDs(2), -}, { - Name: "invalid ExtID", - NameIDs: invalidIdentityNameIDs(3), -}, { - Name: "invalid ExtID", - NameIDs: invalidIdentityNameIDs(4), -}, { - Name: "invalid ExtID", - NameIDs: invalidIdentityNameIDs(5), -}} - -func TestValidIdentityNameIDs(t *testing.T) { - for _, test := range validIdentityNameIDsTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - valid := ValidIdentityNameIDs(test.NameIDs) - if test.Valid { - assert.True(valid) - } else { - assert.False(valid) - } - }) - } -} - -func validIdentity() (i Identity) { - i.ChainID = NewBytes32(validIdentityChainID()) - return -} - -var identityTests = []struct { - Name string - FactomServer string - Valid bool - Error string - Height uint64 - ID1Key *ID1Key - Identity -}{{ - Name: "valid", - Valid: true, - Identity: validIdentity(), - Height: 140744, - ID1Key: newID1Key(hexToBytes( - "9656dbf91feb7d464971f31b28bfbf38ab201b8e33ec69ea4681e3bef779858e")), -}, { - Name: "nil chain ID", - Error: "ChainID is nil", - Identity: Identity{}, -}, { - Name: "bad factomd endpoint", - FactomServer: "http://localhost:1000", - Identity: validIdentity(), - Error: "Post http://localhost:1000/v2: dial tcp [::1]:1000: connect: connection refused", -}, { - Name: "malformed chain", - Identity: NewIdentity(NewBytes32(hexToBytes( - "8888885c2e0b523d9b8ab6d2975639e431eaba3fc9039ead32ce5065dcde86e4"))), -}, { - Name: "invalid chain id", - Identity: NewIdentity(NewBytes32(hexToBytes( - "0088885c2e0b523d9b8ab6d2975639e431eaba3fc9039ead32ce5065dcde86e4"))), -}, { - Name: "non-existent chain id", - Identity: NewIdentity(NewBytes32(hexToBytes( - "8888880000000000000000000000000000000000000000000000000000000000"))), - Error: `jsonrpc2.Error{Code:-32009, Message:"Missing Chain Head"}`, -}} - -var factomServer = "https://courtesy-node.factom.com" - -var c = NewClient() - -func TestIdentity(t *testing.T) { - for _, test := range identityTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - if len(test.FactomServer) == 0 { - test.FactomServer = factomServer - } - c.FactomdServer = test.FactomServer - i := test.Identity - err := i.Get(c) - populated := i.IsPopulated() - if len(test.Error) > 0 { - assert.EqualError(err, test.Error) - } else { - require.NoError(err) - } - if !test.Valid { - assert.False(populated) - return - } - assert.True(populated) - assert.Equal(int(test.Height), int(i.Height)) - assert.Equal(*test.ID1Key, i.ID1) - assert.NoError(i.Get(c)) - }) - } -} diff --git a/factom/idkey.go b/factom/idkey.go deleted file mode 100644 index a3eb228..0000000 --- a/factom/idkey.go +++ /dev/null @@ -1,70 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "crypto/sha256" - - "golang.org/x/crypto/ed25519" -) - -// IDKey is the interface implemented by the four ID and SK Key types. -type IDKey interface { - // PrefixBytes returns the prefix bytes for the key. - PrefixBytes() []byte - // PrefixString returns the encoded prefix string for the key. - PrefixString() string - - // String encodes the key to a base58check string with the appropriate - // prefix. - String() string - // Payload returns the key as a byte array. - Payload() [sha256.Size]byte - // RCDHash returns the RCDHash as a byte array. For IDxKeys, this is - // identical to Payload. For SKxKeys the RCDHash is computed. - RCDHash() [sha256.Size]byte - - // IDKey returns the corresponding IDxKey in an IDKey interface. - // IDxKeys return themselves. Private SKxKeys compute the - // corresponding IDxKey. - IDKey() IDKey -} - -// SKKey is the interface implemented by the four SK Key types. -type SKKey interface { - IDKey - - // SKKey returns the SKKey interface. IDxKeys return themselves. - // Private SKxKeys compute the corresponding IDxKey. - SKKey() SKKey - - // RCD returns the RCD corresponding to the private key. - RCD() []byte - - // PrivateKey returns the ed25519.PrivateKey which can be used for - // signing data. - PrivateKey() ed25519.PrivateKey - // PublicKey returns the ed25519.PublicKey which can be used for - // verifying signatures. - PublicKey() ed25519.PublicKey -} diff --git a/factom/idkey.tmpl b/factom/idkey.tmpl deleted file mode 100644 index 74f67cc..0000000 --- a/factom/idkey.tmpl +++ /dev/null @@ -1,250 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Code generated DO NOT EDIT - -package factom - -// Defines IDKeys ID1Key - ID4Key and corresponding SKKeys SK1Key - SK4Key. - -var ( -{{range . -}} - id{{.ID}}PrefixBytes = [...]byte{ {{.IDPrefix}} } -{{end}} - -{{range . -}} - sk{{.ID}}PrefixBytes = [...]byte{ {{.SKPrefix}} } -{{end}} -) - -const ( -{{range . -}} - id{{.ID}}PrefixStr = "id{{.ID}}" -{{end}} - -{{range . -}} - sk{{.ID}}PrefixStr = "sk{{.ID}}" -{{end}} -) - -var ( -{{range . -}} - _ IDKey = ID{{.ID}}Key{} -{{end}} - -{{range . -}} - _ SKKey = SK{{.ID}}Key{} -{{end}} -) - -{{range .}} -// ID{{.ID}}Key is the id{{.ID}} public key for an identity. -type ID{{.ID}}Key [sha256.Size]byte - -// SK{{.ID}}Key is the sk{{.ID}} secret key for an identity. -type SK{{.ID}}Key [sha256.Size]byte - -// Payload returns key as a byte array. -func (key ID{{.ID}}Key) Payload() [sha256.Size]byte { - return key -} - -// Payload returns key as a byte array. -func (key SK{{.ID}}Key) Payload() [sha256.Size]byte { - return key -} - -// payload returns adr as payload. This is syntactic sugar useful in other -// methods that leverage payload. -func (key ID{{.ID}}Key) payload() payload { - return payload(key) -} -func (key SK{{.ID}}Key) payload() payload { - return payload(key) -} - -// payloadPtr returns adr as *payload. This is syntactic sugar useful in other -// methods that leverage *payload. -func (key *ID{{.ID}}Key) payloadPtr() *payload { - return (*payload)(key) -} -func (key *SK{{.ID}}Key) payloadPtr() *payload { - return (*payload)(key) -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{ {{- .IDPrefix -}} }. -func (ID{{.ID}}Key) PrefixBytes() []byte { - prefix := id{{.ID}}PrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{ {{- .SKPrefix -}} }. -func (SK{{.ID}}Key) PrefixBytes() []byte { - prefix := sk{{.ID}}PrefixBytes - return prefix[:] -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "id{{.ID}}". -func (ID{{.ID}}Key) PrefixString() string { - return id{{.ID}}PrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "sk{{.ID}}". -func (SK{{.ID}}Key) PrefixString() string { - return sk{{.ID}}PrefixStr -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key ID{{.ID}}Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key SK{{.ID}}Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (ID{{.ID}}Key) Type() string { - return id{{.ID}}PrefixStr -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (SK{{.ID}}Key) Type() string { - return sk{{.ID}}PrefixStr -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key ID{{.ID}}Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key SK{{.ID}}Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// NewID{{.ID}}Key attempts to parse keyStr into a new ID{{.ID}}Key. -func NewID{{.ID}}Key(keyStr string) (key ID{{.ID}}Key, err error) { - err = key.Set(keyStr) - return -} - -// NewSK{{.ID}}Key attempts to parse keyStr into a new SK{{.ID}}Key. -func NewSK{{.ID}}Key(keyStr string) (key SK{{.ID}}Key, err error) { - err = key.Set(keyStr) - return -} - -// GenerateSK{{.ID}}Key generates a secure random private Entry Credit address using -// crypto/rand.Random as the source of randomness. -func GenerateSK{{.ID}}Key() (SK{{.ID}}Key, error) { - return generatePrivKey() -} - -// Set attempts to parse keyStr into key. -func (key *ID{{.ID}}Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// Set attempts to parse keyStr into key. -func (key *SK{{.ID}}Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable id{{.ID}} key into key. -func (key *ID{{.ID}}Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable sk{{.ID}} key into key. -func (key *SK{{.ID}}Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// IDKey returns key as an IDKey. -func (key ID{{.ID}}Key) IDKey() IDKey { - return key -} - -// IDKey returns the ID{{.ID}}Key corresponding to key as an IDKey. -func (key SK{{.ID}}Key) IDKey() IDKey { - return key.ID{{.ID}}Key() -} - -// SKKey returns key as an SKKey. -func (key SK{{.ID}}Key) SKKey() SKKey { - return key -} - -// ID{{.ID}}Key computes the ID{{.ID}}Key corresponding to key. -func (key SK{{.ID}}Key) ID{{.ID}}Key() ID{{.ID}}Key { - return key.RCDHash() -} - -// RCDHash returns the RCD hash encoded in key. -func (key ID{{.ID}}Key) RCDHash() [sha256.Size]byte { - return key -} - -// RCDHash computes the RCD hash corresponding to key. -func (key SK{{.ID}}Key) RCDHash() [sha256.Size]byte { - return sha256d(key.RCD()) -} - -// RCD computes the RCD for key. -func (key SK{{.ID}}Key) RCD() []byte { - return append([]byte{RCDType}, key.PublicKey()[:]...) -} - -// PublicKey computes the ed25519.PublicKey for key. -func (key SK{{.ID}}Key) PublicKey() ed25519.PublicKey { - return key.PrivateKey().Public().(ed25519.PublicKey) -} - -// PrivateKey returns the ed25519.PrivateKey for key. -func (key SK{{.ID}}Key) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(key[:]) -} - -// Scan implements sql.Scanner for key using Bytes32.Scan. The ID{{.ID}}Key type is -// not encoded and is assumed. -func (key *ID{{.ID}}Key) Scan(v interface{}) error { - return (*Bytes32)(key).Scan(v) -} - -// Value implements driver.Valuer for key using Bytes32.Value. The ID{{.ID}}Key type -// is not encoded. -func (key ID{{.ID}}Key) Value() (driver.Value, error) { - return (Bytes32)(key).Value() -} -{{end}} diff --git a/factom/idkey_gen.go b/factom/idkey_gen.go deleted file mode 100644 index 3ee389d..0000000 --- a/factom/idkey_gen.go +++ /dev/null @@ -1,834 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Code generated DO NOT EDIT - -package factom - -import ( - "crypto/sha256" - "database/sql/driver" - - "golang.org/x/crypto/ed25519" -) - -// Defines IDKeys ID1Key - ID4Key and corresponding SKKeys SK1Key - SK4Key. - -var ( - id1PrefixBytes = [...]byte{0x3f, 0xbe, 0xba} - id2PrefixBytes = [...]byte{0x3f, 0xbe, 0xd8} - id3PrefixBytes = [...]byte{0x3f, 0xbe, 0xf6} - id4PrefixBytes = [...]byte{0x3f, 0xbf, 0x14} - - sk1PrefixBytes = [...]byte{0x4d, 0xb6, 0xc9} - sk2PrefixBytes = [...]byte{0x4d, 0xb6, 0xe7} - sk3PrefixBytes = [...]byte{0x4d, 0xb7, 0x05} - sk4PrefixBytes = [...]byte{0x4d, 0xb7, 0x23} -) - -const ( - id1PrefixStr = "id1" - id2PrefixStr = "id2" - id3PrefixStr = "id3" - id4PrefixStr = "id4" - - sk1PrefixStr = "sk1" - sk2PrefixStr = "sk2" - sk3PrefixStr = "sk3" - sk4PrefixStr = "sk4" -) - -var ( - _ IDKey = ID1Key{} - _ IDKey = ID2Key{} - _ IDKey = ID3Key{} - _ IDKey = ID4Key{} - - _ SKKey = SK1Key{} - _ SKKey = SK2Key{} - _ SKKey = SK3Key{} - _ SKKey = SK4Key{} -) - -// ID1Key is the id1 public key for an identity. -type ID1Key [sha256.Size]byte - -// SK1Key is the sk1 secret key for an identity. -type SK1Key [sha256.Size]byte - -// Payload returns key as a byte array. -func (key ID1Key) Payload() [sha256.Size]byte { - return key -} - -// Payload returns key as a byte array. -func (key SK1Key) Payload() [sha256.Size]byte { - return key -} - -// payload returns adr as payload. This is syntactic sugar useful in other -// methods that leverage payload. -func (key ID1Key) payload() payload { - return payload(key) -} -func (key SK1Key) payload() payload { - return payload(key) -} - -// payloadPtr returns adr as *payload. This is syntactic sugar useful in other -// methods that leverage *payload. -func (key *ID1Key) payloadPtr() *payload { - return (*payload)(key) -} -func (key *SK1Key) payloadPtr() *payload { - return (*payload)(key) -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x3f, 0xbe, 0xba}. -func (ID1Key) PrefixBytes() []byte { - prefix := id1PrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x4d, 0xb6, 0xc9}. -func (SK1Key) PrefixBytes() []byte { - prefix := sk1PrefixBytes - return prefix[:] -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "id1". -func (ID1Key) PrefixString() string { - return id1PrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "sk1". -func (SK1Key) PrefixString() string { - return sk1PrefixStr -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key ID1Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key SK1Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (ID1Key) Type() string { - return id1PrefixStr -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (SK1Key) Type() string { - return sk1PrefixStr -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key ID1Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key SK1Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// NewID1Key attempts to parse keyStr into a new ID1Key. -func NewID1Key(keyStr string) (key ID1Key, err error) { - err = key.Set(keyStr) - return -} - -// NewSK1Key attempts to parse keyStr into a new SK1Key. -func NewSK1Key(keyStr string) (key SK1Key, err error) { - err = key.Set(keyStr) - return -} - -// GenerateSK1Key generates a secure random private Entry Credit address using -// crypto/rand.Random as the source of randomness. -func GenerateSK1Key() (SK1Key, error) { - return generatePrivKey() -} - -// Set attempts to parse keyStr into key. -func (key *ID1Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// Set attempts to parse keyStr into key. -func (key *SK1Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable id1 key into key. -func (key *ID1Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable sk1 key into key. -func (key *SK1Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// IDKey returns key as an IDKey. -func (key ID1Key) IDKey() IDKey { - return key -} - -// IDKey returns the ID1Key corresponding to key as an IDKey. -func (key SK1Key) IDKey() IDKey { - return key.ID1Key() -} - -// SKKey returns key as an SKKey. -func (key SK1Key) SKKey() SKKey { - return key -} - -// ID1Key computes the ID1Key corresponding to key. -func (key SK1Key) ID1Key() ID1Key { - return key.RCDHash() -} - -// RCDHash returns the RCD hash encoded in key. -func (key ID1Key) RCDHash() [sha256.Size]byte { - return key -} - -// RCDHash computes the RCD hash corresponding to key. -func (key SK1Key) RCDHash() [sha256.Size]byte { - return sha256d(key.RCD()) -} - -// RCD computes the RCD for key. -func (key SK1Key) RCD() []byte { - return append([]byte{RCDType}, key.PublicKey()[:]...) -} - -// PublicKey computes the ed25519.PublicKey for key. -func (key SK1Key) PublicKey() ed25519.PublicKey { - return key.PrivateKey().Public().(ed25519.PublicKey) -} - -// PrivateKey returns the ed25519.PrivateKey for key. -func (key SK1Key) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(key[:]) -} - -// Scan implements sql.Scanner for key using Bytes32.Scan. The ID1Key type is -// not encoded and is assumed. -func (key *ID1Key) Scan(v interface{}) error { - return (*Bytes32)(key).Scan(v) -} - -// Value implements driver.Valuer for key using Bytes32.Value. The ID1Key type -// is not encoded. -func (key ID1Key) Value() (driver.Value, error) { - return (Bytes32)(key).Value() -} - -// ID2Key is the id2 public key for an identity. -type ID2Key [sha256.Size]byte - -// SK2Key is the sk2 secret key for an identity. -type SK2Key [sha256.Size]byte - -// Payload returns key as a byte array. -func (key ID2Key) Payload() [sha256.Size]byte { - return key -} - -// Payload returns key as a byte array. -func (key SK2Key) Payload() [sha256.Size]byte { - return key -} - -// payload returns adr as payload. This is syntactic sugar useful in other -// methods that leverage payload. -func (key ID2Key) payload() payload { - return payload(key) -} -func (key SK2Key) payload() payload { - return payload(key) -} - -// payloadPtr returns adr as *payload. This is syntactic sugar useful in other -// methods that leverage *payload. -func (key *ID2Key) payloadPtr() *payload { - return (*payload)(key) -} -func (key *SK2Key) payloadPtr() *payload { - return (*payload)(key) -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x3f, 0xbe, 0xd8}. -func (ID2Key) PrefixBytes() []byte { - prefix := id2PrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x4d, 0xb6, 0xe7}. -func (SK2Key) PrefixBytes() []byte { - prefix := sk2PrefixBytes - return prefix[:] -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "id2". -func (ID2Key) PrefixString() string { - return id2PrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "sk2". -func (SK2Key) PrefixString() string { - return sk2PrefixStr -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key ID2Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key SK2Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (ID2Key) Type() string { - return id2PrefixStr -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (SK2Key) Type() string { - return sk2PrefixStr -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key ID2Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key SK2Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// NewID2Key attempts to parse keyStr into a new ID2Key. -func NewID2Key(keyStr string) (key ID2Key, err error) { - err = key.Set(keyStr) - return -} - -// NewSK2Key attempts to parse keyStr into a new SK2Key. -func NewSK2Key(keyStr string) (key SK2Key, err error) { - err = key.Set(keyStr) - return -} - -// GenerateSK2Key generates a secure random private Entry Credit address using -// crypto/rand.Random as the source of randomness. -func GenerateSK2Key() (SK2Key, error) { - return generatePrivKey() -} - -// Set attempts to parse keyStr into key. -func (key *ID2Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// Set attempts to parse keyStr into key. -func (key *SK2Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable id2 key into key. -func (key *ID2Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable sk2 key into key. -func (key *SK2Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// IDKey returns key as an IDKey. -func (key ID2Key) IDKey() IDKey { - return key -} - -// IDKey returns the ID2Key corresponding to key as an IDKey. -func (key SK2Key) IDKey() IDKey { - return key.ID2Key() -} - -// SKKey returns key as an SKKey. -func (key SK2Key) SKKey() SKKey { - return key -} - -// ID2Key computes the ID2Key corresponding to key. -func (key SK2Key) ID2Key() ID2Key { - return key.RCDHash() -} - -// RCDHash returns the RCD hash encoded in key. -func (key ID2Key) RCDHash() [sha256.Size]byte { - return key -} - -// RCDHash computes the RCD hash corresponding to key. -func (key SK2Key) RCDHash() [sha256.Size]byte { - return sha256d(key.RCD()) -} - -// RCD computes the RCD for key. -func (key SK2Key) RCD() []byte { - return append([]byte{RCDType}, key.PublicKey()[:]...) -} - -// PublicKey computes the ed25519.PublicKey for key. -func (key SK2Key) PublicKey() ed25519.PublicKey { - return key.PrivateKey().Public().(ed25519.PublicKey) -} - -// PrivateKey returns the ed25519.PrivateKey for key. -func (key SK2Key) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(key[:]) -} - -// Scan implements sql.Scanner for key using Bytes32.Scan. The ID2Key type is -// not encoded and is assumed. -func (key *ID2Key) Scan(v interface{}) error { - return (*Bytes32)(key).Scan(v) -} - -// Value implements driver.Valuer for key using Bytes32.Value. The ID2Key type -// is not encoded. -func (key ID2Key) Value() (driver.Value, error) { - return (Bytes32)(key).Value() -} - -// ID3Key is the id3 public key for an identity. -type ID3Key [sha256.Size]byte - -// SK3Key is the sk3 secret key for an identity. -type SK3Key [sha256.Size]byte - -// Payload returns key as a byte array. -func (key ID3Key) Payload() [sha256.Size]byte { - return key -} - -// Payload returns key as a byte array. -func (key SK3Key) Payload() [sha256.Size]byte { - return key -} - -// payload returns adr as payload. This is syntactic sugar useful in other -// methods that leverage payload. -func (key ID3Key) payload() payload { - return payload(key) -} -func (key SK3Key) payload() payload { - return payload(key) -} - -// payloadPtr returns adr as *payload. This is syntactic sugar useful in other -// methods that leverage *payload. -func (key *ID3Key) payloadPtr() *payload { - return (*payload)(key) -} -func (key *SK3Key) payloadPtr() *payload { - return (*payload)(key) -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x3f, 0xbe, 0xf6}. -func (ID3Key) PrefixBytes() []byte { - prefix := id3PrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x4d, 0xb7, 0x05}. -func (SK3Key) PrefixBytes() []byte { - prefix := sk3PrefixBytes - return prefix[:] -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "id3". -func (ID3Key) PrefixString() string { - return id3PrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "sk3". -func (SK3Key) PrefixString() string { - return sk3PrefixStr -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key ID3Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key SK3Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (ID3Key) Type() string { - return id3PrefixStr -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (SK3Key) Type() string { - return sk3PrefixStr -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key ID3Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key SK3Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// NewID3Key attempts to parse keyStr into a new ID3Key. -func NewID3Key(keyStr string) (key ID3Key, err error) { - err = key.Set(keyStr) - return -} - -// NewSK3Key attempts to parse keyStr into a new SK3Key. -func NewSK3Key(keyStr string) (key SK3Key, err error) { - err = key.Set(keyStr) - return -} - -// GenerateSK3Key generates a secure random private Entry Credit address using -// crypto/rand.Random as the source of randomness. -func GenerateSK3Key() (SK3Key, error) { - return generatePrivKey() -} - -// Set attempts to parse keyStr into key. -func (key *ID3Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// Set attempts to parse keyStr into key. -func (key *SK3Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable id3 key into key. -func (key *ID3Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable sk3 key into key. -func (key *SK3Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// IDKey returns key as an IDKey. -func (key ID3Key) IDKey() IDKey { - return key -} - -// IDKey returns the ID3Key corresponding to key as an IDKey. -func (key SK3Key) IDKey() IDKey { - return key.ID3Key() -} - -// SKKey returns key as an SKKey. -func (key SK3Key) SKKey() SKKey { - return key -} - -// ID3Key computes the ID3Key corresponding to key. -func (key SK3Key) ID3Key() ID3Key { - return key.RCDHash() -} - -// RCDHash returns the RCD hash encoded in key. -func (key ID3Key) RCDHash() [sha256.Size]byte { - return key -} - -// RCDHash computes the RCD hash corresponding to key. -func (key SK3Key) RCDHash() [sha256.Size]byte { - return sha256d(key.RCD()) -} - -// RCD computes the RCD for key. -func (key SK3Key) RCD() []byte { - return append([]byte{RCDType}, key.PublicKey()[:]...) -} - -// PublicKey computes the ed25519.PublicKey for key. -func (key SK3Key) PublicKey() ed25519.PublicKey { - return key.PrivateKey().Public().(ed25519.PublicKey) -} - -// PrivateKey returns the ed25519.PrivateKey for key. -func (key SK3Key) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(key[:]) -} - -// Scan implements sql.Scanner for key using Bytes32.Scan. The ID3Key type is -// not encoded and is assumed. -func (key *ID3Key) Scan(v interface{}) error { - return (*Bytes32)(key).Scan(v) -} - -// Value implements driver.Valuer for key using Bytes32.Value. The ID3Key type -// is not encoded. -func (key ID3Key) Value() (driver.Value, error) { - return (Bytes32)(key).Value() -} - -// ID4Key is the id4 public key for an identity. -type ID4Key [sha256.Size]byte - -// SK4Key is the sk4 secret key for an identity. -type SK4Key [sha256.Size]byte - -// Payload returns key as a byte array. -func (key ID4Key) Payload() [sha256.Size]byte { - return key -} - -// Payload returns key as a byte array. -func (key SK4Key) Payload() [sha256.Size]byte { - return key -} - -// payload returns adr as payload. This is syntactic sugar useful in other -// methods that leverage payload. -func (key ID4Key) payload() payload { - return payload(key) -} -func (key SK4Key) payload() payload { - return payload(key) -} - -// payloadPtr returns adr as *payload. This is syntactic sugar useful in other -// methods that leverage *payload. -func (key *ID4Key) payloadPtr() *payload { - return (*payload)(key) -} -func (key *SK4Key) payloadPtr() *payload { - return (*payload)(key) -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x3f, 0xbf, 0x14}. -func (ID4Key) PrefixBytes() []byte { - prefix := id4PrefixBytes - return prefix[:] -} - -// PrefixBytes returns the two byte prefix for the address type as a byte -// array. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns []byte{0x4d, 0xb7, 0x23}. -func (SK4Key) PrefixBytes() []byte { - prefix := sk4PrefixBytes - return prefix[:] -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "id4". -func (ID4Key) PrefixString() string { - return id4PrefixStr -} - -// PrefixString returns the two prefix bytes for the address type as an encoded -// string. Note that the prefix for a given address type is always the same and -// does not depend on the address value. Returns "sk4". -func (SK4Key) PrefixString() string { - return sk4PrefixStr -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key ID4Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// String encodes key into its human readable form: a base58check string with -// key.PrefixBytes(). -func (key SK4Key) String() string { - return key.payload().StringPrefix(key.PrefixBytes()) -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (ID4Key) Type() string { - return id4PrefixStr -} - -// Type returns PrefixString() satisfies the pflag.Value interface. -func (SK4Key) Type() string { - return sk4PrefixStr -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key ID4Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// MarshalJSON encodes key as a JSON string using key.String(). -func (key SK4Key) MarshalJSON() ([]byte, error) { - return key.payload().MarshalJSONPrefix(key.PrefixBytes()) -} - -// NewID4Key attempts to parse keyStr into a new ID4Key. -func NewID4Key(keyStr string) (key ID4Key, err error) { - err = key.Set(keyStr) - return -} - -// NewSK4Key attempts to parse keyStr into a new SK4Key. -func NewSK4Key(keyStr string) (key SK4Key, err error) { - err = key.Set(keyStr) - return -} - -// GenerateSK4Key generates a secure random private Entry Credit address using -// crypto/rand.Random as the source of randomness. -func GenerateSK4Key() (SK4Key, error) { - return generatePrivKey() -} - -// Set attempts to parse keyStr into key. -func (key *ID4Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// Set attempts to parse keyStr into key. -func (key *SK4Key) Set(keyStr string) error { - return key.payloadPtr().SetPrefix(keyStr, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable id4 key into key. -func (key *ID4Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// UnmarshalJSON decodes a JSON string with a human readable sk4 key into key. -func (key *SK4Key) UnmarshalJSON(data []byte) error { - return key.payloadPtr().UnmarshalJSONPrefix(data, key.PrefixString()) -} - -// IDKey returns key as an IDKey. -func (key ID4Key) IDKey() IDKey { - return key -} - -// IDKey returns the ID4Key corresponding to key as an IDKey. -func (key SK4Key) IDKey() IDKey { - return key.ID4Key() -} - -// SKKey returns key as an SKKey. -func (key SK4Key) SKKey() SKKey { - return key -} - -// ID4Key computes the ID4Key corresponding to key. -func (key SK4Key) ID4Key() ID4Key { - return key.RCDHash() -} - -// RCDHash returns the RCD hash encoded in key. -func (key ID4Key) RCDHash() [sha256.Size]byte { - return key -} - -// RCDHash computes the RCD hash corresponding to key. -func (key SK4Key) RCDHash() [sha256.Size]byte { - return sha256d(key.RCD()) -} - -// RCD computes the RCD for key. -func (key SK4Key) RCD() []byte { - return append([]byte{RCDType}, key.PublicKey()[:]...) -} - -// PublicKey computes the ed25519.PublicKey for key. -func (key SK4Key) PublicKey() ed25519.PublicKey { - return key.PrivateKey().Public().(ed25519.PublicKey) -} - -// PrivateKey returns the ed25519.PrivateKey for key. -func (key SK4Key) PrivateKey() ed25519.PrivateKey { - return ed25519.NewKeyFromSeed(key[:]) -} - -// Scan implements sql.Scanner for key using Bytes32.Scan. The ID4Key type is -// not encoded and is assumed. -func (key *ID4Key) Scan(v interface{}) error { - return (*Bytes32)(key).Scan(v) -} - -// Value implements driver.Valuer for key using Bytes32.Value. The ID4Key type -// is not encoded. -func (key ID4Key) Value() (driver.Value, error) { - return (Bytes32)(key).Value() -} diff --git a/factom/idkey_gen_test.go b/factom/idkey_gen_test.go deleted file mode 100644 index d22932e..0000000 --- a/factom/idkey_gen_test.go +++ /dev/null @@ -1,488 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Code generated DO NOT EDIT - -package factom - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - // Test id/sk key pairs with all zeros. - // OBVIOUSLY NEVER USE THESE FOR ANYTHING! - id1KeyStr = "id12K4tCXKcJJYxJmZ1UY9EuKPvtGVAjo32xySMKNUahbmRcsqFgW" - id2KeyStr = "id22pNvsaMWf9qxWFrmfQpwFJiKQoWfKmBwVgQtdvqVZuqzGmrFNY" - id3KeyStr = "id33pRgpm8ufXNGxtW7n5FgdGP6afXKjU4LfVmgfC8Yaq6LyYq2wA" - id4KeyStr = "id42vYqBB63eoSz8DHozEwtCaLbEwvBTG9pWgD3D5CCaHWy1gCjF5" - - sk1KeyStr = "sk13iLKJfxNQg8vpSmjacEgEQAnXkn7rbjd5ewexc1Un5wVPa7KTk" - sk2KeyStr = "sk22UaDys2Mzg2pUCsToo9aKgxubJFnZN5Bc2LXfV59VxMvXXKwXa" - sk3KeyStr = "sk32Xyo9kmjtNqRUfRd3ZhU56NZd8M1nR61tdBaCLSQRdhUCk4yiM" - sk4KeyStr = "sk43eMusQuvvChoGNn1VZZwbAH8BtKJSZNC7ZWoz1Vc4Y3greLA45" -) - -type idKeyUnmarshalJSONTest struct { - Name string - ID IDKey - ExpID IDKey - Data string - Err string -} - -var idKeyUnmarshalJSONTests = []idKeyUnmarshalJSONTest{{ - Name: "valid/ID1", - Data: fmt.Sprintf("%q", id1KeyStr), - ID: new(ID1Key), - ExpID: func() *ID1Key { - sk, _ := NewSK1Key(sk1KeyStr) - id := sk.ID1Key() - return &id - }(), -}, { - Name: "valid/ID2", - Data: fmt.Sprintf("%q", id2KeyStr), - ID: new(ID2Key), - ExpID: func() *ID2Key { - sk, _ := NewSK2Key(sk2KeyStr) - id := sk.ID2Key() - return &id - }(), -}, { - Name: "valid/ID3", - Data: fmt.Sprintf("%q", id3KeyStr), - ID: new(ID3Key), - ExpID: func() *ID3Key { - sk, _ := NewSK3Key(sk3KeyStr) - id := sk.ID3Key() - return &id - }(), -}, { - Name: "valid/ID4", - Data: fmt.Sprintf("%q", id4KeyStr), - ID: new(ID4Key), - ExpID: func() *ID4Key { - sk, _ := NewSK4Key(sk4KeyStr) - id := sk.ID4Key() - return &id - }(), -}, { - - Name: "valid/SK1", - Data: fmt.Sprintf("%q", sk1KeyStr), - ID: new(SK1Key), - ExpID: func() *SK1Key { - key, _ := NewSK1Key(sk1KeyStr) - return &key - }(), -}, { - Name: "valid/SK2", - Data: fmt.Sprintf("%q", sk2KeyStr), - ID: new(SK2Key), - ExpID: func() *SK2Key { - key, _ := NewSK2Key(sk2KeyStr) - return &key - }(), -}, { - Name: "valid/SK3", - Data: fmt.Sprintf("%q", sk3KeyStr), - ID: new(SK3Key), - ExpID: func() *SK3Key { - key, _ := NewSK3Key(sk3KeyStr) - return &key - }(), -}, { - Name: "valid/SK4", - Data: fmt.Sprintf("%q", sk4KeyStr), - ID: new(SK4Key), - ExpID: func() *SK4Key { - key, _ := NewSK4Key(sk4KeyStr) - return &key - }(), -}, { - - Name: "invalid type", - Data: `{}`, - Err: "json: cannot unmarshal object into Go value of type string", -}, { - Name: "invalid type", - Data: `5.5`, - Err: "json: cannot unmarshal number into Go value of type string", -}, { - Name: "invalid type", - Data: `["hello"]`, - Err: "json: cannot unmarshal array into Go value of type string", -}, { - Name: "invalid length", - Data: fmt.Sprintf("%q", id1KeyStr[0:len(id1KeyStr)-1]), - Err: "invalid length", -}, { - Name: "invalid length", - Data: fmt.Sprintf("%q", id1KeyStr+"Q"), - Err: "invalid length", -}, { - Name: "invalid prefix", - Data: fmt.Sprintf("%q", func() string { - key, _ := NewSK1Key(sk1KeyStr) - return key.payload().StringPrefix([]byte{0x50, 0x50, 0x50}) - }()), - Err: "invalid prefix", -}, { - Name: "invalid symbol/ID1", - Data: fmt.Sprintf("%q", id1KeyStr[0:len(id1KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(ID1Key), - ExpID: new(ID1Key), -}, { - Name: "invalid symbol/SK1", - Data: fmt.Sprintf("%q", sk1KeyStr[0:len(sk1KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(SK1Key), - ExpID: new(SK1Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", id1KeyStr[0:len(id1KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(ID1Key), - ExpID: new(ID1Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", sk1KeyStr[0:len(sk1KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(SK1Key), - ExpID: new(SK1Key), -}, { - Name: "invalid symbol/ID2", - Data: fmt.Sprintf("%q", id2KeyStr[0:len(id2KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(ID2Key), - ExpID: new(ID2Key), -}, { - Name: "invalid symbol/SK2", - Data: fmt.Sprintf("%q", sk2KeyStr[0:len(sk2KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(SK2Key), - ExpID: new(SK2Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", id2KeyStr[0:len(id2KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(ID2Key), - ExpID: new(ID2Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", sk2KeyStr[0:len(sk2KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(SK2Key), - ExpID: new(SK2Key), -}, { - Name: "invalid symbol/ID3", - Data: fmt.Sprintf("%q", id3KeyStr[0:len(id3KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(ID3Key), - ExpID: new(ID3Key), -}, { - Name: "invalid symbol/SK3", - Data: fmt.Sprintf("%q", sk3KeyStr[0:len(sk3KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(SK3Key), - ExpID: new(SK3Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", id3KeyStr[0:len(id3KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(ID3Key), - ExpID: new(ID3Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", sk3KeyStr[0:len(sk3KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(SK3Key), - ExpID: new(SK3Key), -}, { - Name: "invalid symbol/ID4", - Data: fmt.Sprintf("%q", id4KeyStr[0:len(id4KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(ID4Key), - ExpID: new(ID4Key), -}, { - Name: "invalid symbol/SK4", - Data: fmt.Sprintf("%q", sk4KeyStr[0:len(sk4KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(SK4Key), - ExpID: new(SK4Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", id4KeyStr[0:len(id4KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(ID4Key), - ExpID: new(ID4Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", sk4KeyStr[0:len(sk4KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(SK4Key), - ExpID: new(SK4Key), -}} - -func testIDKeyUnmarshalJSON(t *testing.T, test idKeyUnmarshalJSONTest) { - err := json.Unmarshal([]byte(test.Data), test.ID) - assert := assert.New(t) - if len(test.Err) > 0 { - assert.EqualError(err, test.Err) - return - } - assert.NoError(err) - assert.Equal(test.ExpID, test.ID) -} - -func TestIDKey(t *testing.T) { - for _, test := range idKeyUnmarshalJSONTests { - if test.ID != nil { - t.Run("UnmarshalJSON/"+test.Name, func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - continue - } - - test.ExpID, test.ID = new(ID1Key), new(ID1Key) - t.Run("UnmarshalJSON/"+test.Name+"/ID1Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - test.ExpID, test.ID = new(SK1Key), new(SK1Key) - t.Run("UnmarshalJSON/"+test.Name+"/SK1Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - - test.ExpID, test.ID = new(ID2Key), new(ID2Key) - t.Run("UnmarshalJSON/"+test.Name+"/ID2Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - test.ExpID, test.ID = new(SK2Key), new(SK2Key) - t.Run("UnmarshalJSON/"+test.Name+"/SK2Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - - test.ExpID, test.ID = new(ID3Key), new(ID3Key) - t.Run("UnmarshalJSON/"+test.Name+"/ID3Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - test.ExpID, test.ID = new(SK3Key), new(SK3Key) - t.Run("UnmarshalJSON/"+test.Name+"/SK3Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - - test.ExpID, test.ID = new(ID4Key), new(ID4Key) - t.Run("UnmarshalJSON/"+test.Name+"/ID4Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - test.ExpID, test.ID = new(SK4Key), new(SK4Key) - t.Run("UnmarshalJSON/"+test.Name+"/SK4Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - - } - - id1, _ := NewID1Key(id1KeyStr) - sk1, _ := NewSK1Key(sk1KeyStr) - id2, _ := NewID2Key(id2KeyStr) - sk2, _ := NewSK2Key(sk2KeyStr) - id3, _ := NewID3Key(id3KeyStr) - sk3, _ := NewSK3Key(sk3KeyStr) - id4, _ := NewID4Key(id4KeyStr) - sk4, _ := NewSK4Key(sk4KeyStr) - - strToKey := map[string]IDKey{ - id1KeyStr: id1, sk1KeyStr: sk1, - id2KeyStr: id2, sk2KeyStr: sk2, - id3KeyStr: id3, sk3KeyStr: sk3, - id4KeyStr: id4, sk4KeyStr: sk4, - } - for keyStr, key := range strToKey { - t.Run("MarshalJSON/"+key.PrefixString(), func(t *testing.T) { - data, err := json.Marshal(key) - assert := assert.New(t) - assert.NoError(err) - assert.Equal(fmt.Sprintf("%q", keyStr), string(data)) - }) - t.Run("Payload/"+key.PrefixString(), func(t *testing.T) { - assert.EqualValues(t, key, key.Payload()) - }) - t.Run("String/"+key.PrefixString(), func(t *testing.T) { - assert.Equal(t, keyStr, key.String()) - }) - } - - t.Run("SKKey/SK1", func(t *testing.T) { - id, _ := NewID1Key(id1KeyStr) - sk, _ := NewSK1Key(sk1KeyStr) - assert := assert.New(t) - assert.Equal(id, sk.ID1Key()) - assert.Equal(id.IDKey(), sk.IDKey()) - assert.Equal(SKKey(sk), sk.SKKey()) - assert.Equal(id.RCDHash(), sk.RCDHash(), "RCDHash") - }) - t.Run("SKKey/SK2", func(t *testing.T) { - id, _ := NewID2Key(id2KeyStr) - sk, _ := NewSK2Key(sk2KeyStr) - assert := assert.New(t) - assert.Equal(id, sk.ID2Key()) - assert.Equal(id.IDKey(), sk.IDKey()) - assert.Equal(SKKey(sk), sk.SKKey()) - assert.Equal(id.RCDHash(), sk.RCDHash(), "RCDHash") - }) - t.Run("SKKey/SK3", func(t *testing.T) { - id, _ := NewID3Key(id3KeyStr) - sk, _ := NewSK3Key(sk3KeyStr) - assert := assert.New(t) - assert.Equal(id, sk.ID3Key()) - assert.Equal(id.IDKey(), sk.IDKey()) - assert.Equal(SKKey(sk), sk.SKKey()) - assert.Equal(id.RCDHash(), sk.RCDHash(), "RCDHash") - }) - t.Run("SKKey/SK4", func(t *testing.T) { - id, _ := NewID4Key(id4KeyStr) - sk, _ := NewSK4Key(sk4KeyStr) - assert := assert.New(t) - assert.Equal(id, sk.ID4Key()) - assert.Equal(id.IDKey(), sk.IDKey()) - assert.Equal(SKKey(sk), sk.SKKey()) - assert.Equal(id.RCDHash(), sk.RCDHash(), "RCDHash") - }) - - t.Run("Generate/SK1", func(t *testing.T) { - _, err := GenerateSK1Key() - assert.NoError(t, err) - }) - t.Run("Generate/SK2", func(t *testing.T) { - _, err := GenerateSK2Key() - assert.NoError(t, err) - }) - t.Run("Generate/SK3", func(t *testing.T) { - _, err := GenerateSK3Key() - assert.NoError(t, err) - }) - t.Run("Generate/SK4", func(t *testing.T) { - _, err := GenerateSK4Key() - assert.NoError(t, err) - }) - - t.Run("Scan", func(t *testing.T) { - var id ID1Key - err := id.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = id.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = id.Scan(in) - assert.NoError(err) - assert.EqualValues(in, id[:]) - }) - t.Run("Scan", func(t *testing.T) { - var id ID2Key - err := id.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = id.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = id.Scan(in) - assert.NoError(err) - assert.EqualValues(in, id[:]) - }) - t.Run("Scan", func(t *testing.T) { - var id ID3Key - err := id.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = id.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = id.Scan(in) - assert.NoError(err) - assert.EqualValues(in, id[:]) - }) - t.Run("Scan", func(t *testing.T) { - var id ID4Key - err := id.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = id.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = id.Scan(in) - assert.NoError(err) - assert.EqualValues(in, id[:]) - }) - - t.Run("Value", func(t *testing.T) { - var id ID1Key - id[0] = 0xff - val, err := id.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(id[:], val) - }) - t.Run("Value", func(t *testing.T) { - var id ID2Key - id[0] = 0xff - val, err := id.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(id[:], val) - }) - t.Run("Value", func(t *testing.T) { - var id ID3Key - id[0] = 0xff - val, err := id.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(id[:], val) - }) - t.Run("Value", func(t *testing.T) { - var id ID4Key - id[0] = 0xff - val, err := id.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(id[:], val) - }) - -} diff --git a/factom/idkey_test.tmpl b/factom/idkey_test.tmpl deleted file mode 100644 index d5d0d94..0000000 --- a/factom/idkey_test.tmpl +++ /dev/null @@ -1,225 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Code generated DO NOT EDIT - -package factom - -var ( - // Test id/sk key pairs with all zeros. - // OBVIOUSLY NEVER USE THESE FOR ANYTHING! -{{range . -}} - id{{.ID}}KeyStr = "{{.IDStr}}" -{{end}} - -{{range . -}} - sk{{.ID}}KeyStr = "{{.SKStr}}" -{{end}} -) - -type idKeyUnmarshalJSONTest struct { - Name string - ID IDKey - ExpID IDKey - Data string - Err string -} - -var idKeyUnmarshalJSONTests = []idKeyUnmarshalJSONTest{ { -{{ range . -}} - Name: "valid/ID{{.ID}}", - Data: fmt.Sprintf("%q", id{{.ID}}KeyStr), - ID: new(ID{{.ID}}Key), - ExpID: func() *ID{{.ID}}Key { - sk, _ := NewSK{{.ID}}Key(sk{{.ID}}KeyStr) - id := sk.ID{{.ID}}Key() - return &id - }(), -}, { -{{ end }} -{{ range . -}} - Name: "valid/SK{{.ID}}", - Data: fmt.Sprintf("%q", sk{{.ID}}KeyStr), - ID: new(SK{{.ID}}Key), - ExpID: func() *SK{{.ID}}Key { - key, _ := NewSK{{.ID}}Key(sk{{.ID}}KeyStr) - return &key - }(), -}, { -{{end}} - Name: "invalid type", - Data: `{}`, - Err: "json: cannot unmarshal object into Go value of type string", -}, { - Name: "invalid type", - Data: `5.5`, - Err: "json: cannot unmarshal number into Go value of type string", -}, { - Name: "invalid type", - Data: `["hello"]`, - Err: "json: cannot unmarshal array into Go value of type string", -}, { - Name: "invalid length", - Data: fmt.Sprintf("%q", id1KeyStr[0:len(id1KeyStr)-1]), - Err: "invalid length", -}, { - Name: "invalid length", - Data: fmt.Sprintf("%q", id1KeyStr+"Q"), - Err: "invalid length", -}, { - Name: "invalid prefix", - Data: fmt.Sprintf("%q", func() string { - key, _ := NewSK1Key(sk1KeyStr) - return key.payload().StringPrefix([]byte{0x50, 0x50, 0x50}) - }()), - Err: "invalid prefix", -{{ range . -}} -}, { - Name: "invalid symbol/ID{{.ID}}", - Data: fmt.Sprintf("%q", id{{.ID}}KeyStr[0:len(id{{.ID}}KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(ID{{.ID}}Key), - ExpID: new(ID{{.ID}}Key), -}, { - Name: "invalid symbol/SK{{.ID}}", - Data: fmt.Sprintf("%q", sk{{.ID}}KeyStr[0:len(sk{{.ID}}KeyStr)-1]+"0"), - Err: "invalid format: version and/or checksum bytes missing", - ID: new(SK{{.ID}}Key), - ExpID: new(SK{{.ID}}Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", id{{.ID}}KeyStr[0:len(id{{.ID}}KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(ID{{.ID}}Key), - ExpID: new(ID{{.ID}}Key), -}, { - Name: "invalid checksum", - Data: fmt.Sprintf("%q", sk{{.ID}}KeyStr[0:len(sk{{.ID}}KeyStr)-1]+"e"), - Err: "checksum error", - ID: new(SK{{.ID}}Key), - ExpID: new(SK{{.ID}}Key), -{{end}} -} } - -func testIDKeyUnmarshalJSON(t *testing.T, test idKeyUnmarshalJSONTest) { - err := json.Unmarshal([]byte(test.Data), test.ID) - assert := assert.New(t) - if len(test.Err) > 0 { - assert.EqualError(err, test.Err) - return - } - assert.NoError(err) - assert.Equal(test.ExpID, test.ID) -} - -func TestIDKey(t *testing.T) { - for _, test := range idKeyUnmarshalJSONTests { - if test.ID != nil { - t.Run("UnmarshalJSON/"+test.Name, func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - continue - } -{{range .}} - test.ExpID, test.ID = new(ID{{.ID}}Key), new(ID{{.ID}}Key) - t.Run("UnmarshalJSON/"+test.Name+"/ID{{.ID}}Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) - test.ExpID, test.ID = new(SK{{.ID}}Key), new(SK{{.ID}}Key) - t.Run("UnmarshalJSON/"+test.Name+"/SK{{.ID}}Key", func(t *testing.T) { - testIDKeyUnmarshalJSON(t, test) - }) -{{end}} - } - -{{range . -}} - id{{.ID}}, _ := NewID{{.ID}}Key(id{{.ID}}KeyStr) - sk{{.ID}}, _ := NewSK{{.ID}}Key(sk{{.ID}}KeyStr) -{{end}} - strToKey := map[string]IDKey{ -{{range . -}} - id{{.ID}}KeyStr: id{{.ID}}, sk{{.ID}}KeyStr: sk{{.ID}}, -{{end}} - } - for keyStr, key := range strToKey { - t.Run("MarshalJSON/"+key.PrefixString(), func(t *testing.T) { - data, err := json.Marshal(key) - assert := assert.New(t) - assert.NoError(err) - assert.Equal(fmt.Sprintf("%q", keyStr), string(data)) - }) - t.Run("Payload/"+key.PrefixString(), func(t *testing.T) { - assert.EqualValues(t, key, key.Payload()) - }) - t.Run("String/"+key.PrefixString(), func(t *testing.T) { - assert.Equal(t, keyStr, key.String()) - }) - } - -{{range . -}} - t.Run("SKKey/SK{{.ID}}", func(t *testing.T) { - id, _ := NewID{{.ID}}Key(id{{.ID}}KeyStr) - sk, _ := NewSK{{.ID}}Key(sk{{.ID}}KeyStr) - assert := assert.New(t) - assert.Equal(id, sk.ID{{.ID}}Key()) - assert.Equal(id.IDKey(), sk.IDKey()) - assert.Equal(SKKey(sk), sk.SKKey()) - assert.Equal(id.RCDHash(), sk.RCDHash(), "RCDHash") - }) -{{end}} - -{{range . -}} - t.Run("Generate/SK{{.ID}}", func(t *testing.T) { - _, err := GenerateSK{{.ID}}Key() - assert.NoError(t, err) - }) -{{end}} - -{{range . -}} - t.Run("Scan", func(t *testing.T) { - var id ID{{.ID}}Key - err := id.Scan(5) - assert := assert.New(t) - assert.EqualError(err, "invalid type") - - in := make([]byte, 32) - in[0] = 0xff - err = id.Scan(in[:10]) - assert.EqualError(err, "invalid length") - - err = id.Scan(in) - assert.NoError(err) - assert.EqualValues(in, id[:]) - }) -{{end}} - -{{range . -}} - t.Run("Value", func(t *testing.T) { - var id ID{{.ID}}Key - id[0] = 0xff - val, err := id.Value() - assert := assert.New(t) - assert.NoError(err) - assert.Equal(id[:], val) - }) -{{end}} -} diff --git a/factom/networkid.go b/factom/networkid.go deleted file mode 100644 index cbdb830..0000000 --- a/factom/networkid.go +++ /dev/null @@ -1,71 +0,0 @@ -package factom - -import ( - "fmt" - "strings" -) - -var ( - mainnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA2} - testnetID = [...]byte{0x88, 0x3e, 0x09, 0x3b} - localnetID = [...]byte{0xFA, 0x92, 0xE5, 0xA4} -) - -func MainnetID() NetworkID { return mainnetID } -func TestnetID() NetworkID { return testnetID } -func LocalnetID() NetworkID { return localnetID } - -type NetworkID [4]byte - -func (n NetworkID) String() string { - switch n { - case mainnetID: - return "mainnet" - case testnetID: - return "testnet" - case localnetID: - return "localnet" - default: - return "custom: 0x" + Bytes(n[:]).String() - } -} -func (n *NetworkID) Set(netIDStr string) error { - switch strings.ToLower(netIDStr) { - case "main", "mainnet": - *n = mainnetID - case "test", "testnet": - *n = testnetID - case "local", "localnet": - *n = localnetID - default: - if netIDStr[:2] == "0x" { - // omit leading 0x - netIDStr = netIDStr[2:] - } - var b Bytes - if err := b.Set(netIDStr); err != nil { - return err - } - if len(b) != len(n[:]) { - return fmt.Errorf("invalid length") - } - copy(n[:], b) - } - return nil -} - -func (n NetworkID) IsMainnet() bool { - return n == mainnetID -} - -func (n NetworkID) IsTestnet() bool { - return n == testnetID -} - -func (n NetworkID) IsLocalnet() bool { - return n == localnetID -} - -func (n NetworkID) IsCustom() bool { - return !n.IsMainnet() && !n.IsTestnet() -} diff --git a/factom/payload.go b/factom/payload.go deleted file mode 100644 index e078918..0000000 --- a/factom/payload.go +++ /dev/null @@ -1,72 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - - "github.com/Factom-Asset-Tokens/base58" -) - -// payload implements helper functions used by all Address and IDKey types. -type payload [sha256.Size]byte - -// StringPrefix encodes payload as a base58check string with the given prefix. -func (pld payload) StringPrefix(prefix []byte) string { - return base58.CheckEncode(pld[:], prefix...) -} - -// MarshalJSONPrefix encodes payload as a base58check JSON string with the -// given prefix. -func (pld payload) MarshalJSONPrefix(prefix []byte) ([]byte, error) { - return []byte(fmt.Sprintf("%q", pld.StringPrefix(prefix))), nil -} - -// SetPrefix attempts to parse adrStr into adr enforcing that adrStr -// starts with prefix if prefix is not empty. -func (pld *payload) SetPrefix(str, prefix string) error { - if len(str) != 50+len(prefix) { - return fmt.Errorf("invalid length") - } - if len(prefix) > 0 && str[:len(prefix)] != prefix { - return fmt.Errorf("invalid prefix") - } - b, _, err := base58.CheckDecode(str, len(prefix)) - if err != nil { - return err - } - copy(pld[:], b) - return nil -} - -// UnmarshalJSONPrefix unmarshals a human readable address JSON string with the -// given prefix. -func (pld *payload) UnmarshalJSONPrefix(data []byte, prefix string) error { - var str string - if err := json.Unmarshal(data, &str); err != nil { - return err - } - return pld.SetPrefix(str, prefix) -} diff --git a/factom/pendingentries.go b/factom/pendingentries.go deleted file mode 100644 index dc7388e..0000000 --- a/factom/pendingentries.go +++ /dev/null @@ -1,90 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "bytes" - "sort" -) - -// PendingEntries is a list of pending entries which may or may not be -// revealed. If the entry's ChainID is not nil, then its data has been revealed -// and can be queried from factomd. -type PendingEntries []Entry - -// Get returns all pending entries sorted by descending ChainID, and then order -// they were originally returned. Pending Entries that are committed but not -// revealed have a nil ChainID and are at the end of the pe slice. -func (pe *PendingEntries) Get(c *Client) error { - if err := c.FactomdRequest("pending-entries", nil, pe); err != nil { - return err - } - sort.SliceStable(*pe, func(i, j int) bool { - pe := *pe - var ci, cj []byte - ei, ej := pe[i], pe[j] - if ei.ChainID != nil { - ci = ei.ChainID[:] - } - if ej.ChainID != nil { - cj = ej.ChainID[:] - } - return bytes.Compare(ci, cj) > 0 - }) - return nil -} - -// Entries efficiently finds and returns all entries in pe for the given -// chainID, if any exist. Otherwise, Entries returns nil. -func (pe PendingEntries) Entries(chainID *Bytes32) []Entry { - var cID []byte - if chainID != nil { - cID = chainID[:] - } - // Find the first index of the entry with this chainID. - ei := sort.Search(len(pe), func(i int) bool { - var c []byte - e := pe[i] - if e.ChainID != nil { - c = e.ChainID[:] - } - return bytes.Compare(c, cID) <= 0 - }) - if chainID == nil { - // Unrevealed entries with no ChainID are all at the end, if any. - return pe[ei:] - } - if ei == len(pe) || // (None are true) OR - // (We found nil OR we did not find an exact match) - (pe[ei].ChainID == nil || *pe[ei].ChainID != *chainID) { - // There are no entries for this ChainID. - return nil - } - // Find all remaining entries with the chainID. - for i, e := range pe[ei:] { - if e.ChainID == nil || *e.ChainID != *chainID { - return pe[ei : ei+i] - } - } - return pe[ei:] -} diff --git a/factom/pendingentries_test.go b/factom/pendingentries_test.go deleted file mode 100644 index e4f7a2d..0000000 --- a/factom/pendingentries_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import ( - "bytes" - "fmt" - "sort" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var searchID = NewBytes32FromString( - "b0bb9c3e14514d7109b18b8edf3875618bac0881935e20a14b69d81fdc39e624") - -// TestPendingEntries is not a stable test. It depends on the current pending -// entries. It could be improved with using static data, but it works for now. -// Some tests are only performed if the pending entries for the test are -// available. But all of these test cases have been executed and manually -// verified. -func TestPendingEntries(t *testing.T) { - var pe PendingEntries - c := NewClient() - //c.Factomd.DebugRequest = true - assert := assert.New(t) - require := require.New(t) - require.NoError(pe.Get(c)) - - if len(pe) == 0 { - return - } - - fmt.Printf("pe: %+v\n", pe) - - require.True(sort.SliceIsSorted(pe, func(i, j int) bool { - var ci, cj []byte - ei, ej := pe[i], pe[j] - if ei.ChainID != nil { - ci = ei.ChainID[:] - } - if ej.ChainID != nil { - cj = ej.ChainID[:] - } - return bytes.Compare(ci, cj) > 0 - }), "not sorted") - - es := pe.Entries(&Bytes32{}) - if len(es) > 0 { - assert.Nil(es[0].ChainID) - } - chainID := pe[len(pe)-1].ChainID - if chainID != nil { - es := pe.Entries(chainID) - require.NotEmpty(es) - for _, e := range es { - assert.Equal(*e.ChainID, *chainID) - } - } - - es = pe.Entries(searchID) - if len(es) == 0 { - return - } - fmt.Printf("es: %+v\n", es) - for _, e := range es { - assert.Equal(*e.ChainID, *searchID) - } -} diff --git a/factom/rcdprivatekey.go b/factom/rcdprivatekey.go deleted file mode 100644 index 32a647f..0000000 --- a/factom/rcdprivatekey.go +++ /dev/null @@ -1,39 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package factom - -import "golang.org/x/crypto/ed25519" - -// RCDPrivateKey is the interface implemented by the four SK Key types and the -// Fs Address type. -type RCDPrivateKey interface { - // RCD returns the RCD corresponding to the private key. - RCD() []byte - - // PrivateKey returns the ed25519.PrivateKey which can be used for - // signing data. - PrivateKey() ed25519.PrivateKey - // PublicKey returns the ed25519.PublicKey which can be used for - // verifying signatures. - PublicKey() ed25519.PublicKey -} diff --git a/factom/varintf/varintf.go b/factom/varintf/varintf.go deleted file mode 100644 index efadd2d..0000000 --- a/factom/varintf/varintf.go +++ /dev/null @@ -1,71 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Package varintf implements Factom's varInt_F specification. -// -// The varInt_F specifications uses the top bit (0x80) in each byte as the -// continuation bit. If this bit is set, continue to read the next byte. If -// this bit is not set, then this is the last byte. The remaining 7 bits are -// the actual data of the number. The bytes are ordered big endian, unlike the -// varInt used by protobuf or provided by package encoding/binary. -// -// https://github.com/FactomProject/FactomDocs/blob/master/factomDataStructureDetails.md#variable-integers-varint_f -package varintf - -import ( - "math/bits" -) - -const continuationBitMask = 0x80 - -// Encode x into varInt_F bytes. -func Encode(x uint64) []byte { - bitlen := bits.Len64(x) - buflen := bitlen / 7 - if bitlen == 0 || bitlen%7 > 0 { - buflen++ - } - buf := make([]byte, buflen) - for i := range buf { - buf[i] = continuationBitMask | uint8(x>>uint((buflen-i-1)*7)) - } - // Unset continuation bit in last byte. - buf[buflen-1] &^= continuationBitMask - return buf -} - -// Decode varInt_F bytes into a uint64 and return the number of bytes used. If -// buf encodes a number larger than 64 bits, 0 and -1 is returned. -func Decode(buf []byte) (uint64, int) { - buflen := 1 - for b := buf[0]; b&continuationBitMask > 0; b = buf[buflen-1] { - buflen++ - } - if buflen > 10 || (buflen == 10 && buf[0] > 0x81) { - return 0, -1 - } - var x uint64 - for i := 0; i < buflen; i++ { - x |= uint64(buf[i]&^continuationBitMask) << uint((buflen-i-1)*7) - } - return x, buflen -} diff --git a/factom/varintf/varintf_test.go b/factom/varintf/varintf_test.go deleted file mode 100644 index 2e2c66a..0000000 --- a/factom/varintf/varintf_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package varintf - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestEncodeDecode(t *testing.T) { - assert := assert.New(t) - for x := uint64(1); x > 0; x <<= 1 { - buf := Encode(x) - d, l := Decode(buf) - assert.Equalf(x, d, "%x", int(x)) - assert.Equalf(len(buf), l, "%x", int(x)) - } -} - -var testFactomSpecExamples = []struct { - X uint64 - Buf []byte -}{{ - X: 0, - Buf: []byte{0}, -}, { - X: 3, - Buf: []byte{3}, -}, { - X: 127, - Buf: []byte{127}, -}, { - X: 128, - // 10000001 00000000 - Buf: []byte{0x81, 0}, -}, { - X: 130, - // 10000001 00000010 - Buf: []byte{0x81, 2}, -}, { - X: (1 << 16) - 1, // 2^16 - 1 - // 10000011 11111111 01111111 - Buf: []byte{0x83, 0xff, 0x7f}, -}, { - X: 1 << 16, // 2^16 - // 10000100 10000000 00000000 - Buf: []byte{0x84, 0x80, 0}, -}, { - X: (1 << 32) - 1, // 2^32 - 1 - // 10001111 11111111 11111111 11111111 01111111 - Buf: []byte{0x8f, 0xff, 0xff, 0xff, 0x7f}, -}, { - X: 1 << 32, // 2^32 - // 10010000 10000000 10000000 10000000 00000000 - Buf: []byte{0x90, 0x80, 0x80, 0x80, 0x00}, -}, { - X: (1 << 63) - 1, // 2^63 - 1 - // 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 01111111 - Buf: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, -}, { - X: (1 << 64) - 1, // 2^64 - 1 - // 10000001 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 01111111 - Buf: []byte{0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, -}} - -func TestFactomSpecExamples(t *testing.T) { - assert := assert.New(t) - for _, test := range testFactomSpecExamples { - buf := Encode(test.X) - x, l := Decode(test.Buf) - assert.Equalf(test.Buf, buf, "%x", int(test.X)) - assert.Equalf(test.X, x, "%x", int(test.X)) - assert.Equalf(len(buf), l, "%x", int(test.X)) - } -} - -func BenchmarkDecode(b *testing.B) { - var buf []byte - for i := 0; i < b.N; i++ { - buf = Encode(uint64((1 << uint(i%64)) - i)) - } - _ = buf -} -func BenchmarkEncodeDecode(b *testing.B) { - var buf []byte - var x uint64 - var l int - for i := 0; i < b.N; i++ { - buf = Encode(uint64((1 << uint(i%64)) - i)) - x, l = Decode(buf) - } - _ = buf - _ = x - _ = l - -} diff --git a/fat/chainid.go b/fat/chainid.go index 6d6d341..b46c8bd 100644 --- a/fat/chainid.go +++ b/fat/chainid.go @@ -25,7 +25,7 @@ package fat import ( "unicode/utf8" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" ) // ValidTokenNameIDs returns true if the nameIDs match the pattern for a valid diff --git a/fat/entry.go b/fat/entry.go index 2578a64..2c5b670 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -31,7 +31,7 @@ import ( "strconv" "time" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" "golang.org/x/crypto/ed25519" diff --git a/fat/entry_test.go b/fat/entry_test.go index d2e5ebb..11e54ad 100644 --- a/fat/entry_test.go +++ b/fat/entry_test.go @@ -28,7 +28,7 @@ import ( "testing" "time" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" . "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/stretchr/testify/assert" ) diff --git a/fat/fat0/addressamountmap.go b/fat/fat0/addressamountmap.go index 206924b..360c996 100644 --- a/fat/fat0/addressamountmap.go +++ b/fat/fat0/addressamountmap.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index 3d48db2..b3d3533 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) diff --git a/fat/fat0/transaction_test.go b/fat/fat0/transaction_test.go index ff6c610..903d5b4 100644 --- a/fat/fat0/transaction_test.go +++ b/fat/fat0/transaction_test.go @@ -28,7 +28,7 @@ import ( "fmt" "testing" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" . "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/stretchr/testify/assert" diff --git a/fat/fat1/addressnftokensmap.go b/fat/fat1/addressnftokensmap.go index 2794a3c..1c3f0f1 100644 --- a/fat/fat1/addressnftokensmap.go +++ b/fat/fat1/addressnftokensmap.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) diff --git a/fat/fat1/addressnftokensmap_test.go b/fat/fat1/addressnftokensmap_test.go index 4fae647..72caf8f 100644 --- a/fat/fat1/addressnftokensmap_test.go +++ b/fat/fat1/addressnftokensmap_test.go @@ -26,7 +26,7 @@ import ( "encoding/json" "testing" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/stretchr/testify/assert" ) diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index d4a4a08..ec77333 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) diff --git a/fat/fat1/transaction_test.go b/fat/fat1/transaction_test.go index 1e74239..0310262 100644 --- a/fat/fat1/transaction_test.go +++ b/fat/fat1/transaction_test.go @@ -28,7 +28,7 @@ import ( "fmt" "testing" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" . "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/stretchr/testify/assert" diff --git a/fat/issuance.go b/fat/issuance.go index 4a2eab9..c555632 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) diff --git a/fat/issuance_test.go b/fat/issuance_test.go index 0ee086f..3c7772e 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -27,7 +27,7 @@ import ( "encoding/json" "testing" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" . "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/flag/flag.go b/flag/flag.go index cd3a7b2..94a8514 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -30,7 +30,7 @@ import ( "strconv" "time" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/posener/complete" "github.com/sirupsen/logrus" ) diff --git a/flag/list.go b/flag/list.go index f4ebac7..0657196 100644 --- a/flag/list.go +++ b/flag/list.go @@ -25,7 +25,7 @@ package flag import ( "strings" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" ) type FAAddressList []factom.FAAddress diff --git a/flag/predict.go b/flag/predict.go index dd72de1..6b3b9f5 100644 --- a/flag/predict.go +++ b/flag/predict.go @@ -28,7 +28,7 @@ import ( "strings" "time" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/posener/complete" ) diff --git a/go.mod b/go.mod index d17a5ab..36ac6e5 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 + github.com/Factom-Asset-Tokens/factom v0.0.0-20190911030001-cff277216c13 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/magiconair/properties v1.8.1 // indirect diff --git a/go.sum b/go.sum index 1a1965d..cd23d08 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= +github.com/Factom-Asset-Tokens/factom v0.0.0-20190911030001-cff277216c13 h1:gNX1sqf6Z2zmXi5jIpEeRY4hgKVk5DC7E71ci+DWbzA= +github.com/Factom-Asset-Tokens/factom v0.0.0-20190911030001-cff277216c13/go.mod h1:vzneQd1WXinMysQlIlgxin51zWW8yEte9tU0ylHxOZY= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/srv/methods.go b/srv/methods.go index c788c53..c532f57 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -31,7 +31,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/engine" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" diff --git a/srv/methods_test.go b/srv/methods_test.go index fbc093d..ffdbe27 100644 --- a/srv/methods_test.go +++ b/srv/methods_test.go @@ -33,7 +33,7 @@ import ( "time" jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/srv/params.go b/srv/params.go index e079a6e..bde5a6b 100644 --- a/srv/params.go +++ b/srv/params.go @@ -27,7 +27,7 @@ import ( "time" jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) From 9a2b5e79508bab3a84828ac7a91e5029a63a061e Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 11 Sep 2019 12:46:37 -0800 Subject: [PATCH 048/124] build(go.mod): Update all package dependencies --- fat/entry.go | 2 +- fat/fat0/transaction_test.go | 48 +++--- fat/fat1/transaction_test.go | 48 +++--- go.mod | 16 +- go.sum | 31 ++-- srv/methods_test.go | 303 ----------------------------------- 6 files changed, 62 insertions(+), 386 deletions(-) delete mode 100644 srv/methods_test.go diff --git a/fat/entry.go b/fat/entry.go index 2c5b670..9543158 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -34,7 +34,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" - "golang.org/x/crypto/ed25519" + "crypto/ed25519" ) type Transaction interface { diff --git a/fat/fat0/transaction_test.go b/fat/fat0/transaction_test.go index 903d5b4..e46c9fd 100644 --- a/fat/fat0/transaction_test.go +++ b/fat/fat0/transaction_test.go @@ -40,13 +40,13 @@ var transactionTests = []struct { Error string IssuerKey factom.ID1Key Coinbase bool - Tx Transaction + Tx *Transaction }{{ Name: "valid", Tx: validTx(), }, { Name: "valid (single outputs)", - Tx: func() Transaction { + Tx: func() *Transaction { out := outputs() out[outputAddresses[0].FAAddress().String()] += out[outputAddresses[1].FAAddress().String()] + @@ -85,7 +85,7 @@ var transactionTests = []struct { }, { Name: "invalid JSON (invalid inputs, zero amount)", Error: "*fat0.Transaction.Inputs: *fat0.AddressAmountMap: invalid amount (0): ", - Tx: func() Transaction { + Tx: func() *Transaction { in := inputs() in[inputAddresses[0].FAAddress().String()] = 0 return setFieldTransaction("inputs", in) @@ -125,7 +125,7 @@ var transactionTests = []struct { }, { Name: "invalid data (sum mismatch)", Error: "*fat0.Transaction: sum(inputs) != sum(outputs)", - Tx: func() Transaction { + Tx: func() *Transaction { out := outputs() out[outputAddresses[0].FAAddress().String()]++ return setFieldTransaction("outputs", out) @@ -134,7 +134,7 @@ var transactionTests = []struct { Name: "invalid data (coinbase)", Error: "*fat0.Transaction: invalid coinbase transaction", IssuerKey: issuerKey, - Tx: func() Transaction { + Tx: func() *Transaction { m := validCoinbaseTxEntryContentMap() in := coinbaseInputs() in[inputAddresses[0].FAAddress().String()] = 1 @@ -148,7 +148,7 @@ var transactionTests = []struct { Name: "invalid data (coinbase, coinbase outputs)", Error: "*fat0.Transaction: duplicate address: ", IssuerKey: issuerKey, - Tx: func() Transaction { + Tx: func() *Transaction { m := validCoinbaseTxEntryContentMap() in := coinbaseInputs() out := coinbaseOutputs() @@ -161,7 +161,7 @@ var transactionTests = []struct { }, { Name: "invalid data (inputs outputs overlap)", Error: "*fat0.Transaction: duplicate address: ", - Tx: func() Transaction { + Tx: func() *Transaction { m := validTxEntryContentMap() in := inputs() in[outputAddresses[0].FAAddress().String()] = @@ -173,7 +173,7 @@ var transactionTests = []struct { }, { Name: "invalid ExtIDs (timestamp)", Error: "timestamp salt expired", - Tx: func() Transaction { + Tx: func() *Transaction { t := validTx() t.ExtIDs[0] = factom.Bytes("100") return t @@ -181,7 +181,7 @@ var transactionTests = []struct { }, { Name: "invalid ExtIDs (length)", Error: "invalid number of ExtIDs", - Tx: func() Transaction { + Tx: func() *Transaction { t := validTx() t.ExtIDs = append(t.ExtIDs, factom.Bytes{}) return t @@ -193,7 +193,7 @@ var transactionTests = []struct { }, { Name: "RCD input mismatch", Error: "invalid RCDs", - Tx: func() Transaction { + Tx: func() *Transaction { t := validTx() adrs := twoAddresses() t.Sign(adrs[0], adrs[1]) @@ -239,25 +239,25 @@ var ( ) // Transactions -func omitFieldTransaction(field string) Transaction { +func omitFieldTransaction(field string) *Transaction { m := validTxEntryContentMap() delete(m, field) return transaction(marshal(m)) } -func setFieldTransaction(field string, value interface{}) Transaction { +func setFieldTransaction(field string, value interface{}) *Transaction { m := validTxEntryContentMap() m[field] = value return transaction(marshal(m)) } -func validTx() Transaction { +func validTx() *Transaction { return transaction(marshal(validTxEntryContentMap())) } -func coinbaseTx() Transaction { +func coinbaseTx() *Transaction { t := transaction(marshal(validCoinbaseTxEntryContentMap())) t.Sign(issuerSecret) return t } -func transaction(content factom.Bytes) Transaction { +func transaction(content factom.Bytes) *Transaction { e := factom.Entry{ ChainID: &tokenChainID, Content: content, @@ -270,7 +270,7 @@ func transaction(content factom.Bytes) Transaction { t.Sign(adrs...) return t } -func invalidField(field string) Transaction { +func invalidField(field string) *Transaction { m := validTxEntryContentMap() m[field] = []int{0} return transaction(marshal(m)) @@ -327,20 +327,20 @@ func coinbaseOutputs() map[string]uint64 { var transactionMarshalEntryTests = []struct { Name string Error string - Tx Transaction + Tx *Transaction }{{ Name: "valid", Tx: newTransaction(), }, { Name: "valid (omit zero balances)", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Inputs[fat.Coinbase()] = 0 return t }(), }, { Name: "valid (metadata)", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Metadata = json.RawMessage(`{"memo":"Rent for Dec 2018"}`) return t @@ -348,7 +348,7 @@ var transactionMarshalEntryTests = []struct { }, { Name: "invalid data", Error: "json: error calling MarshalJSON for type *fat0.Transaction: sum(inputs) != sum(outputs)", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Inputs[inputAddresses[0].FAAddress()]++ return t @@ -356,7 +356,7 @@ var transactionMarshalEntryTests = []struct { }, { Name: "invalid data", Error: "json: error calling MarshalJSON for type *fat0.Transaction: json: error calling MarshalJSON for type fat0.AddressAmountMap: empty", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Inputs = make(AddressAmountMap) t.Outputs = make(AddressAmountMap) @@ -365,7 +365,7 @@ var transactionMarshalEntryTests = []struct { }, { Name: "invalid metadata JSON", Error: "json: error calling MarshalJSON for type *fat0.Transaction: json: error calling MarshalJSON for type json.RawMessage: invalid character 'a' looking for beginning of object key string", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Metadata = json.RawMessage("{asdf") return t @@ -387,8 +387,8 @@ func TestTransactionMarshalEntry(t *testing.T) { } } -func newTransaction() Transaction { - return Transaction{ +func newTransaction() *Transaction { + return &Transaction{ Inputs: inputAddressAmountMap(), Outputs: outputAddressAmountMap(), } diff --git a/fat/fat1/transaction_test.go b/fat/fat1/transaction_test.go index 0310262..eb2322c 100644 --- a/fat/fat1/transaction_test.go +++ b/fat/fat1/transaction_test.go @@ -40,13 +40,13 @@ var transactionTests = []struct { Error string IssuerKey factom.ID1Key Coinbase bool - Tx Transaction + Tx *Transaction }{{ Name: "valid", Tx: validTx(), }, { Name: "valid (single outputs)", - Tx: func() Transaction { + Tx: func() *Transaction { out := outputs() out[outputAddresses[0].FAAddress().String()]. Append(out[outputAddresses[1].FAAddress().String()]) @@ -110,7 +110,7 @@ var transactionTests = []struct { }, { Name: "invalid data (Input Output mismatch)", Error: "*fat1.Transaction: Inputs and Outputs mismatch: number of NFTokenIDs differ", - Tx: func() Transaction { + Tx: func() *Transaction { out := outputs() NFTokenID(1000).Set(out[outputAddresses[0].FAAddress().String()]) return setFieldTransaction("outputs", out) @@ -118,7 +118,7 @@ var transactionTests = []struct { }, { Name: "invalid data (Input Output mismatch)", Error: "*fat1.Transaction: Inputs and Outputs mismatch: missing NFTokenID: 1000", - Tx: func() Transaction { + Tx: func() *Transaction { in := inputs() NFTokenID(1001).Set(in[inputAddresses[0].FAAddress().String()]) out := outputs() @@ -132,7 +132,7 @@ var transactionTests = []struct { Name: "invalid data (coinbase)", Error: "*fat1.Transaction: invalid coinbase transaction", IssuerKey: issuerKey, - Tx: func() Transaction { + Tx: func() *Transaction { m := validCoinbaseTxEntryContentMap() in := coinbaseInputs() in[inputAddresses[0].FAAddress().String()] = newNFTokens(NFTokenID(1000)) @@ -146,7 +146,7 @@ var transactionTests = []struct { Name: "invalid data (coinbase, coinbase outputs)", Error: "*fat1.Transaction: Inputs and Outputs intersect: duplicate address: ", IssuerKey: issuerKey, - Tx: func() Transaction { + Tx: func() *Transaction { m := validCoinbaseTxEntryContentMap() in := coinbaseInputs() out := coinbaseOutputs() @@ -161,7 +161,7 @@ var transactionTests = []struct { Name: "invalid data (coinbase, tokenmetadata)", Error: "*fat1.Transaction.TokenMetadata: too many NFTokenIDs", IssuerKey: issuerKey, - Tx: func() Transaction { + Tx: func() *Transaction { m := validCoinbaseTxEntryContentMap() in := coinbaseInputs() delete(in[fat.Coinbase().String()], NFTokenID(0)) @@ -175,7 +175,7 @@ var transactionTests = []struct { }, { Name: "invalid data (inputs outputs overlap)", Error: "*fat1.Transaction: Inputs and Outputs intersect: duplicate address: ", - Tx: func() Transaction { + Tx: func() *Transaction { m := validTxEntryContentMap() in := inputs() in[outputAddresses[0].FAAddress().String()] = @@ -187,7 +187,7 @@ var transactionTests = []struct { }, { Name: "invalid ExtIDs (timestamp)", Error: "timestamp salt expired", - Tx: func() Transaction { + Tx: func() *Transaction { t := validTx() t.ExtIDs[0] = factom.Bytes("100") return t @@ -195,7 +195,7 @@ var transactionTests = []struct { }, { Name: "invalid ExtIDs (length)", Error: "invalid number of ExtIDs", - Tx: func() Transaction { + Tx: func() *Transaction { t := validTx() t.ExtIDs = append(t.ExtIDs, factom.Bytes{}) return t @@ -207,7 +207,7 @@ var transactionTests = []struct { }, { Name: "RCD input mismatch", Error: "invalid RCDs", - Tx: func() Transaction { + Tx: func() *Transaction { t := validTx() adrs := twoAddresses() t.Sign(adrs[0], adrs[1]) @@ -264,25 +264,25 @@ func newNFTokens(ids ...NFTokensSetter) NFTokens { } // Transactions -func omitFieldTransaction(field string) Transaction { +func omitFieldTransaction(field string) *Transaction { m := validTxEntryContentMap() delete(m, field) return transaction(marshal(m)) } -func setFieldTransaction(field string, value interface{}) Transaction { +func setFieldTransaction(field string, value interface{}) *Transaction { m := validTxEntryContentMap() m[field] = value return transaction(marshal(m)) } -func validTx() Transaction { +func validTx() *Transaction { return transaction(marshal(validTxEntryContentMap())) } -func coinbaseTx() Transaction { +func coinbaseTx() *Transaction { t := transaction(marshal(validCoinbaseTxEntryContentMap())) t.Sign(issuerSecret) return t } -func transaction(content factom.Bytes) Transaction { +func transaction(content factom.Bytes) *Transaction { e := factom.Entry{ ChainID: &tokenChainID, Content: content, @@ -295,7 +295,7 @@ func transaction(content factom.Bytes) Transaction { t.Sign(adrs...) return t } -func invalidField(field string) Transaction { +func invalidField(field string) *Transaction { m := validTxEntryContentMap() m[field] = []int{0} return transaction(marshal(m)) @@ -359,20 +359,20 @@ func coinbaseOutputs() map[string]NFTokens { var transactionMarshalEntryTests = []struct { Name string Error string - Tx Transaction + Tx *Transaction }{{ Name: "valid", Tx: newTransaction(), }, { Name: "valid (omit zero balances)", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Inputs[fat.Coinbase()], _ = NewNFTokens() return t }(), }, { Name: "valid (metadata)", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Metadata = json.RawMessage(`{"memo":"Rent for Dec 2018"}`) return t @@ -380,7 +380,7 @@ var transactionMarshalEntryTests = []struct { }, { Name: "invalid data", Error: "json: error calling MarshalJSON for type *fat1.Transaction: Inputs and Outputs mismatch: number of NFTokenIDs differ", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Inputs[inputAddresses[0].FAAddress()].Set(NFTokenID(12345)) return t @@ -388,7 +388,7 @@ var transactionMarshalEntryTests = []struct { }, { Name: "invalid metadata JSON", Error: "json: error calling MarshalJSON for type *fat1.Transaction: json: error calling MarshalJSON for type json.RawMessage: invalid character 'a' looking for beginning of object key string", - Tx: func() Transaction { + Tx: func() *Transaction { t := newTransaction() t.Metadata = json.RawMessage("{asdf") return t @@ -410,8 +410,8 @@ func TestTransactionMarshalEntry(t *testing.T) { } } -func newTransaction() Transaction { - return Transaction{ +func newTransaction() *Transaction { + return &Transaction{ Inputs: inputAddressNFTokensMap(), Outputs: outputAddressNFTokensMap(), } diff --git a/go.mod b/go.mod index 36ac6e5..c0e8dc8 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,24 @@ module github.com/Factom-Asset-Tokens/fatd -go 1.12 +go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb - github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e - github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 - github.com/Factom-Asset-Tokens/factom v0.0.0-20190911030001-cff277216c13 + github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect - github.com/magiconair/properties v1.8.1 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 - github.com/pelletier/go-toml v1.4.0 // indirect github.com/posener/complete v1.2.1 - github.com/rs/cors v1.6.0 + github.com/rs/cors v1.7.0 github.com/sirupsen/logrus v1.4.2 - github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cobra v0.0.5 - github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.4.0 - golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 - golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect - golang.org/x/text v0.3.2 // indirect + golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 // indirect ) replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 diff --git a/go.sum b/go.sum index cd23d08..c566295 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20190911030001-cff277216c13 h1:gNX1sqf6Z2zmXi5jIpEeRY4hgKVk5DC7E71ci+DWbzA= -github.com/Factom-Asset-Tokens/factom v0.0.0-20190911030001-cff277216c13/go.mod h1:vzneQd1WXinMysQlIlgxin51zWW8yEte9tU0ylHxOZY= +github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a h1:Yc1KR8xLaJGV+lnyVDnazIhjY+VpmqwJ/cOY4CcJEmw= +github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a/go.mod h1:E1CRixabBfgR9Qb/ND2Q6Mqm9vCD3N9MH+sy60aj2x0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -85,8 +85,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -98,8 +96,6 @@ github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:Jbxf github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pkg/errors v0.8.0/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= @@ -115,8 +111,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -125,16 +121,12 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= @@ -163,15 +155,12 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 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/crypto v0.0.0-20190909091759-094676da4a83 h1:mgAKeshyNqWKdENOnQsg+8dRTwZFIwFaO3HNl52sweA= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -186,18 +175,16 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b h1:3S2h5FadpNr0zUUCVZjlKIEYF+KaX/OBplTGo89CYHI= +golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s= +golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/srv/methods_test.go b/srv/methods_test.go deleted file mode 100644 index ffdbe27..0000000 --- a/srv/methods_test.go +++ /dev/null @@ -1,303 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package srv - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "testing" - "time" - - jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/flag" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -//// We make copies because the original is modified during the method call. -//var tokenParamsRes = cpResponse(TokenParamsRes) -//var tokenNotFoundRes = cpResponse(TokenNotFoundRes) -//var transactionNotFoundRes = cpResponse(TransactionNotFoundRes) -//var getTransactionParamsRes = cpResponse(GetTransactionParamsRes) -//var getTransactionsParamsRes = cpResponse(GetTransactionsParamsRes) -//var getBalanceParamsRes = cpResponse(GetBalanceParamsRes) -var getBalanceValidRes = jrpc.NewResponse(float64(0)) - -var tokenID = "invalid" - -type Test struct { - Params interface{} - Description string - Result interface{} - Error interface{} -} - -var getIssuanceTests = []Test{{ - Description: "nil params", - Error: TokenParamsError, -}, { - Params: TokenParams{}, - Description: "empty params", - Error: TokenParamsError, -}, { - Params: struct { - TokenParams - NewField string - }{TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}, NewField: "hello"}, - Description: "unknown field", - Error: jrpc.NewInvalidParamsError(`json: unknown field "NewField"`), -}, { - Params: TokenParams{ChainID: factom.NewBytes32(nil), TokenID: &tokenID}, - Description: "chain id and token id", - Error: TokenParamsError, -}, { - Params: TokenParams{ChainID: factom.NewBytes32(nil), - IssuerChainID: factom.NewBytes32(nil)}, - Description: "chain id and issuer chain id", - Error: TokenParamsError, -}, { - Params: TokenParams{ChainID: factom.NewBytes32(nil), - IssuerChainID: factom.NewBytes32(nil), TokenID: &tokenID}, - Description: "chain id and token id and issuer chain id", - Error: TokenParamsError, -}, { - Params: TokenParams{IssuerChainID: factom.NewBytes32(nil), - TokenID: &tokenID}, - Description: "token id and issuer chain id", - Error: TokenNotFoundError, -}, { - Params: TokenParams{ChainID: factom.NewBytes32(nil)}, - Description: "chain id", - Error: TokenNotFoundError, -}, -} - -var getTransactionTests = []Test{{ - Params: TokenParams{ChainID: factom.NewBytes32(nil), - IssuerChainID: factom.NewBytes32(nil)}, - Description: "no hash", - Error: GetTransactionParamsError, -}, { - Params: GetTransactionParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}, - Hash: factom.NewBytes32(nil)}, - Description: "tx not found", - Error: TransactionNotFoundError, -}, -} - -var getTransactionsTests = []Test{{ - Params: GetTransactionsParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}, - Hash: factom.NewBytes32(nil), Start: new(uint)}, - Description: "hash and start", - Error: GetTransactionsParamsError, -}, { - Params: GetTransactionsParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}, - Hash: factom.NewBytes32(nil)}, - Description: "tx not found, with hash", - Error: TransactionNotFoundError, -}, { - Params: GetTransactionsParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}, Limit: new(uint)}, - Description: "zero limit", - Error: GetTransactionsParamsError, -}, { - Params: GetTransactionsParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}}, - Description: "tx not found", - Error: TransactionNotFoundError, -}, -} - -var getBalanceTests = []Test{{ - Params: GetBalanceParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}}, - Description: "no address", - Error: GetBalanceParamsError, -}, { - Params: GetBalanceParams{ - Address: &factom.Address{}}, - Description: "no chain", - Error: GetBalanceParamsError, -}, { - Params: GetBalanceParams{TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}, - Address: &factom.Address{}}, - Description: "valid", - Result: 0, -}, -} - -var getStatsTests = []Test{{ - Description: "no params", - Error: TokenParamsError, -}, { - Params: TokenParams{ChainID: factom.NewBytes32(nil)}, - Description: "valid", - Result: struct { - Supply int `json:"supply"` - CirculatingSupply int `json:"circulating-supply"` - Transactions int `json:"transactions"` - IssuanceTimestamp int `json:"issuance-timestamp"` - LastTransactionTimestamp int `json:"last-transaction-timestamp"` - }{}, -}} - -var NFTokenID = "test" - -var getNFTokenTests = []Test{{ - Params: GetNFTokenParams{ - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}}, - Description: "no nf token param", - Error: GetNFTokenParamsError, -}, { - Params: GetNFTokenParams{NonFungibleTokenID: &NFTokenID, - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}}, - Description: "valid", - Error: TokenNotFoundError, -}} - -var sendTransactionTests = []Test{{ - Description: "no params", - Error: SendTransactionParamsError, -}, { - Params: SendTransactionParams{Content: factom.Bytes{0x00}, - ExtIDs: []factom.Bytes{{0x00}}, - TokenParams: TokenParams{ChainID: factom.NewBytes32(nil)}}, - Description: "invalid token", - Error: TokenNotFoundError, -}} - -var getDaemonTokensTests = []Test{{ - Description: "no params", - Result: []struct { - TokenID string `json:"token-id"` - IssuerID *factom.Bytes32 `json:"issuer-id"` - ChainID *factom.Bytes32 `json:"chain-id"` - }{{}}, -}, { - Params: TokenParams{ChainID: factom.NewBytes32(nil)}, - Description: "valid", - Error: NoParamsError, -}} - -var getDaemonPropertiesTests = []Test{{ - Params: []int{0}, - Description: "invalid params", - Error: NoParamsError, -}, { - Description: "no params", - Result: struct { - FatdVersion string `json:"fatd-version"` - APIVersion string `json:"api-version"` - }{FatdVersion: "0.0.0", APIVersion: "v0"}, -}} - -var methodTests = map[string][]Test{ - "get-issuance": getIssuanceTests, - "get-transaction": getTransactionTests, - "get-transactions": getTransactionsTests, - "get-balance": getBalanceTests, - "get-stats": getStatsTests, - "get-nf-token": getNFTokenTests, - "send-transaction": sendTransactionTests, - "get-daemon-tokens": getDaemonTokensTests, - "get-daemon-properties": getDaemonPropertiesTests, -} - -func TestMethods(t *testing.T) { - flag.APIAddress = "localhost:18888" - Start() - for method, tests := range methodTests { - t.Run(method, func(t *testing.T) { - for _, test := range tests { - t.Run(test.Description, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - res, err := request(method, - test.Params, &json.RawMessage{}) - assert.NoError(err) - assert.NotNil(res.ID) - if test.Result != nil { - data, err := json.Marshal(test.Result) - require.NoError(err) - result := res.Result.(*json.RawMessage) - require.NotEmpty(result) - assert.JSONEq(string(data), string(*result), - "Result") - } else { - require.NotNil(res.Error) - assert.Equal(test.Error, *res.Error, "Error") - } - }) - } - }) - } - Stop() -} - -func request(method string, params interface{}, result interface{}) (jrpc.Response, error) { - // Generate a random ID for this request. - id := rand.Uint32()%200 + 500 - - // Marshal the JSON RPC Request. - reqBytes, err := json.Marshal(jrpc.NewRequest(method, id, params)) - if err != nil { - return jrpc.Response{}, err - } - - // Make the HTTP request. - endpoint := "http://" + flag.APIAddress - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(reqBytes)) - if err != nil { - return jrpc.Response{}, err - } - req.Header.Add("Content-Type", "application/json") - c := http.Client{Timeout: 2 * time.Second} - res, err := c.Do(req) - if err != nil { - return jrpc.Response{}, err - } - if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusBadRequest { - return jrpc.Response{}, fmt.Errorf("http: %v", res.Status) - } - - // Read the HTTP response. - resBytes, err := ioutil.ReadAll(res.Body) - if err != nil { - return jrpc.Response{}, fmt.Errorf("ioutil.ReadAll(http.Response.Body): %v", err) - } - - // Unmarshal the HTTP response into a JSON RPC response. - resJrpc := jrpc.NewResponse(result) - if err := json.Unmarshal(resBytes, &resJrpc); err != nil { - return jrpc.Response{}, fmt.Errorf("json.Unmarshal(): %v", err) - } - return resJrpc, nil -} From cc7e0efc56f072d23ddddad7d580ad27d9ebe15a Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 11 Sep 2019 14:40:14 -0800 Subject: [PATCH 049/124] docs(README): Update golang version requirement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34db36c..af36f6e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ dynamically link to the SQLite3 C shared libraries to the `fatd` Golang binary. CGo requires that GCC be available on your system. The following dependencies are required to build `fatd` and `fat-cli`. -- [Golang](https://golang.org/) 1.11.4 or later. The latest official release of +- [Golang](https://golang.org/) 1.13 or later. The latest official release of Golang is always recommended. - [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) is used by the code generation used in the `./factom` package. The code generation step From f78b39d80ed1cc19b190ff9a10507a46ff3576cf Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 11 Sep 2019 14:43:14 -0800 Subject: [PATCH 050/124] fix(srv): Disable 'send-transaction' until implementation is fixed --- srv/methods.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/srv/methods.go b/srv/methods.go index c532f57..fb7ba04 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -29,9 +29,9 @@ import ( jrpc "github.com/AdamSLevy/jsonrpc2/v11" + "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/engine" - "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" @@ -440,6 +440,7 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { + return jrpc.NewError(-34000, "not implemented", "send-transaction") params := ParamsSendTransaction{} chain, put, err := validate(data, ¶ms) if err != nil { From 273adaa88e9e2d2e62822241c71e14c29ebd84ff Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 11 Sep 2019 16:05:44 -0800 Subject: [PATCH 051/124] fix(srv): Correctly implement "send-transaction" --- db/address.go | 21 +++++---- db/apply.go | 15 ++++--- db/chain.go | 2 +- db/entry.go | 9 ++-- db/nftoken.go | 3 +- db/validate.go | 5 ++- srv/methods.go | 113 ++++++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 140 insertions(+), 28 deletions(-) diff --git a/db/address.go b/db/address.go index 7761424..19aa93b 100644 --- a/db/address.go +++ b/db/address.go @@ -56,17 +56,22 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, var sqlitexNoResultsErr = "sqlite: statement has no results" -func SelectAddressBalance(conn *sqlite.Conn, adr *factom.FAAddress) (uint64, error) { - stmt := conn.Prep(`SELECT "balance" FROM "addresses" WHERE "address" = ?;`) +func SelectAddressIDBalance(conn *sqlite.Conn, + adr *factom.FAAddress) (adrID int64, bal uint64, err error) { + adrID = -1 + stmt := conn.Prep(`SELECT "id", "balance" FROM "addresses" WHERE "address" = ?;`) + defer stmt.Reset() stmt.BindBytes(1, adr[:]) - bal, err := sqlitex.ResultInt64(stmt) - if err != nil && err.Error() == sqlitexNoResultsErr { - return 0, nil - } + hasRow, err := stmt.Step() if err != nil { - return 0, err + return + } + if !hasRow { + return } - return uint64(bal), nil + adrID = stmt.ColumnInt64(0) + bal = uint64(stmt.ColumnInt64(1)) + return } func SelectAddressID(conn *sqlite.Conn, adr *factom.FAAddress) (int64, error) { diff --git a/db/apply.go b/db/apply.go index 7340fb4..583d770 100644 --- a/db/apply.go +++ b/db/apply.go @@ -138,22 +138,22 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { if txErr = tx.Validate(chain.ID1); txErr != nil { - return txErr, nil + return } e := tx.FactomEntry() - valid, err := checkEntryUniqueValid(chain.Conn, ei, e.Hash) + valid, err := CheckEntryUniquelyValid(chain.Conn, ei, e.Hash) if err != nil { - return nil, err + return } if !valid { - return fmt.Errorf("replay: hash previously marked valid"), nil + txErr = fmt.Errorf("replay: hash previously marked valid") + return } if err = chain.setEntryValid(ei); err != nil { return } - - return nil, nil + return } func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, @@ -297,7 +297,8 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, if err = chain.setNFTokenOwner(nfID, ai); err != nil { return } - if err = chain.insertNFTokenTransaction(nfID, adrTxID); err != nil { + if err = chain.insertNFTokenTransaction( + nfID, adrTxID); err != nil { return } } diff --git a/db/chain.go b/db/chain.go index 0d02334..a15c371 100644 --- a/db/chain.go +++ b/db/chain.go @@ -229,7 +229,7 @@ func OpenConnPool(dbURI string) (conn *sqlite.Conn, pool *sqlitex.Pool, return } - flags = baseFlags | sqlite.SQLITE_OPEN_READWRITE + flags = baseFlags | sqlite.SQLITE_OPEN_READONLY if pool, err = sqlitex.Open(dbURI, flags, PoolSize); err != nil { err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", dbURI, flags, PoolSize, err) diff --git a/db/entry.go b/db/entry.go index 33b5677..36f479d 100644 --- a/db/entry.go +++ b/db/entry.go @@ -199,12 +199,13 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, return entries, nil } -func checkEntryUniqueValid(conn *sqlite.Conn, +func CheckEntryUniquelyValid(conn *sqlite.Conn, id int64, hash *factom.Bytes32) (bool, error) { stmt := conn.Prep(`SELECT count(*) FROM "entries" WHERE - "valid" = true AND "id" < ? AND "hash" = ?;`) - stmt.BindInt64(1, id) - stmt.BindBytes(2, hash[:]) + "valid" = true AND (? OR "id" < ?) AND "hash" = ?;`) + stmt.BindBool(1, id > 0) + stmt.BindInt64(2, id) + stmt.BindBytes(3, hash[:]) val, err := sqlitex.ResultInt(stmt) if err != nil { return false, err diff --git a/db/nftoken.go b/db/nftoken.go index 0cb1870..ff45a8e 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -78,14 +78,13 @@ func SelectNFToken(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (factom.FAAddress, FROM "nf_tokens_addresses" WHERE "id" = ?;`) stmt.BindInt64(1, int64(nfTkn)) hasRow, err := stmt.Step() + defer stmt.Reset() if err != nil { - stmt.Reset() return owner, creationHash, nil, err } if !hasRow { return owner, creationHash, nil, nil } - defer stmt.Reset() if stmt.ColumnBytes(0, owner[:]) != len(owner) { panic("invalid address length") } diff --git a/db/validate.go b/db/validate.go index 6c9d234..30186e3 100644 --- a/db/validate.go +++ b/db/validate.go @@ -23,6 +23,7 @@ func (chain Chain) Validate() (err error) { chain.Log.Debug("Validating database...") // Validate ChainID... read := chain.Pool.Get(nil) + defer chain.Pool.Put(read) write := chain.Conn first, err := SelectEntryByID(read, 1) if err != nil { @@ -67,7 +68,9 @@ func (chain Chain) Validate() (err error) { chain.setApplyFunc() eBlockStmt := read.Prep(SelectEBlockWhere + `true;`) // SELECT all EBlocks. - entryStmt := read.Prep(SelectEntryWhere + `true;`) // SELECT all Entries. + defer eBlockStmt.Reset() + entryStmt := read.Prep(SelectEntryWhere + `true;`) // SELECT all Entries. + defer entryStmt.Reset() var eID int = 1 // Entry ID var sequence uint32 // EBlock Sequence diff --git a/srv/methods.go b/srv/methods.go index fb7ba04..42b6080 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -216,7 +216,7 @@ func getBalance(data json.RawMessage) interface{} { } defer put() - balance, err := db.SelectAddressBalance(chain.Conn, params.Address) + _, balance, err := db.SelectAddressIDBalance(chain.Conn, params.Address) if err != nil { panic(err) } @@ -263,7 +263,7 @@ func getBalances(data json.RawMessage) interface{} { } conn, put := chain.Get() defer put() - balance, err := db.SelectAddressBalance(conn, params.Address) + _, balance, err := db.SelectAddressIDBalance(conn, params.Address) if err != nil { panic(err) } @@ -325,7 +325,7 @@ func getStats(data json.RawMessage) interface{} { } defer put() - burned, err := db.SelectAddressBalance(chain.Conn, &coinbaseRCDHash) + _, burned, err := db.SelectAddressIDBalance(chain.Conn, &coinbaseRCDHash) if err != nil { panic(err) } @@ -440,7 +440,6 @@ func getNFTokens(data json.RawMessage) interface{} { } func sendTransaction(data json.RawMessage) interface{} { - return jrpc.NewError(-34000, "not implemented", "send-transaction") params := ParamsSendTransaction{} chain, put, err := validate(data, ¶ms) if err != nil { @@ -452,7 +451,13 @@ func sendTransaction(data json.RawMessage) interface{} { } entry := params.Entry() - txErr, err := chain.ApplyEntry(entry) + var txErr error + switch chain.Type { + case fat0.Type: + txErr, err = attemptApplyFAT0Tx(chain, entry) + case fat1.Type: + txErr, err = attemptApplyFAT1Tx(chain, entry) + } if err != nil { panic(err) } @@ -489,6 +494,104 @@ func sendTransaction(data json.RawMessage) interface{} { Hash *factom.Bytes32 `json:"entryhash"` }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} } +func attemptApplyFAT0Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) { + // Validate tx + tx := fat0.NewTransaction(e) + txErr, err = applyTx(chain, tx) + + if tx.IsCoinbase() { + addIssued := tx.Inputs[fat.Coinbase()] + if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + txErr = fmt.Errorf("coinbase exceeds max supply") + return + } + } else { + // Check all input balances + for adr, amount := range tx.Inputs { + var bal uint64 + if _, bal, err = db.SelectAddressIDBalance( + chain.Conn, &adr); err != nil { + return + } + if amount > bal { + txErr = fmt.Errorf("insufficient balance: %v", adr) + return + } + } + } + return +} +func attemptApplyFAT1Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) { + // Validate tx + tx := fat1.NewTransaction(e) + txErr, err = applyTx(chain, tx) + + if tx.IsCoinbase() { + nfTkns := tx.Inputs[fat.Coinbase()] + addIssued := uint64(len(nfTkns)) + if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + txErr = fmt.Errorf("coinbase exceeds max supply") + return + } + for nfID := range nfTkns { + var ownerID int64 + ownerID, err = db.SelectNFTokenOwnerID(chain.Conn, nfID) + if err != nil { + return + } + if ownerID != -1 { + txErr = fmt.Errorf("NFTokenID{%v} already exists", nfID) + return + } + } + } else { + for adr, nfTkns := range tx.Inputs { + var adrID int64 + var bal uint64 + adrID, bal, err = db.SelectAddressIDBalance(chain.Conn, &adr) + if err != nil { + return + } + amount := uint64(len(nfTkns)) + if amount > bal { + txErr = fmt.Errorf("insufficient balance: %v", adr) + return + } + for nfTkn := range nfTkns { + var ownerID int64 + ownerID, err = db.SelectNFTokenOwnerID(chain.Conn, nfTkn) + if err != nil { + return + } + if ownerID == -1 { + txErr = fmt.Errorf("no such NFToken{%v}", nfTkn) + return + } + if ownerID != adrID { + txErr = fmt.Errorf("NFToken{%v} not owned by %v", + nfTkn, adr) + return + } + } + } + } + return +} +func applyTx(chain *engine.Chain, tx fat.Transaction) (txErr, err error) { + if txErr = tx.Validate(chain.ID1); txErr != nil { + return + } + e := tx.FactomEntry() + valid, err := db.CheckEntryUniquelyValid(chain.Conn, 0, e.Hash) + if err != nil { + return + } + if !valid { + txErr = fmt.Errorf("replay: hash previously marked valid") + return + } + return +} func getDaemonTokens(data json.RawMessage) interface{} { if _, _, err := validate(data, nil); err != nil { From e7a7ca1420950997a7df771b625ac8c2111b342c Mon Sep 17 00:00:00 2001 From: Who Soup Date: Mon, 16 Sep 2019 12:34:04 +0200 Subject: [PATCH 052/124] adding missing license headers --- db/address.go | 22 ++++++++++++++++++++++ db/apply.go | 22 ++++++++++++++++++++++ db/chain_test.go | 22 ++++++++++++++++++++++ db/eblock.go | 22 ++++++++++++++++++++++ db/entry.go | 22 ++++++++++++++++++++++ db/gen.go | 22 ++++++++++++++++++++++ db/gentestdb.go | 24 +++++++++++++++++++++++- db/metadata.go | 22 ++++++++++++++++++++++ db/nftoken.go | 22 ++++++++++++++++++++++ db/schema.go | 22 ++++++++++++++++++++++ db/sql.go | 22 ++++++++++++++++++++++ db/validate.go | 22 ++++++++++++++++++++++ 12 files changed, 265 insertions(+), 1 deletion(-) diff --git a/db/address.go b/db/address.go index 19aa93b..9f75d2f 100644 --- a/db/address.go +++ b/db/address.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/apply.go b/db/apply.go index 583d770..7b52213 100644 --- a/db/apply.go +++ b/db/apply.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/chain_test.go b/db/chain_test.go index b6bda20..c03cc08 100644 --- a/db/chain_test.go +++ b/db/chain_test.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/eblock.go b/db/eblock.go index 548e021..10b6362 100644 --- a/db/eblock.go +++ b/db/eblock.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/entry.go b/db/entry.go index 36f479d..9c443c2 100644 --- a/db/entry.go +++ b/db/entry.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/gen.go b/db/gen.go index cd80c44..ea6b241 100644 --- a/db/gen.go +++ b/db/gen.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db //go:generate go run ./gentestdb.go -chainid b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb diff --git a/db/gentestdb.go b/db/gentestdb.go index e8d2c3b..b069909 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + // +build ignore package main @@ -8,8 +30,8 @@ import ( "log" "os" - "github.com/Factom-Asset-Tokens/fatd/db" . "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/fat" fflag "github.com/Factom-Asset-Tokens/fatd/flag" ) diff --git a/db/metadata.go b/db/metadata.go index 01d73d9..b5f4719 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/nftoken.go b/db/nftoken.go index ff45a8e..2a2d6ce 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/schema.go b/db/schema.go index 166dbfc..d50ff9d 100644 --- a/db/schema.go +++ b/db/schema.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/sql.go b/db/sql.go index 3d2be96..6a934c1 100644 --- a/db/sql.go +++ b/db/sql.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( diff --git a/db/validate.go b/db/validate.go index 30186e3..0121432 100644 --- a/db/validate.go +++ b/db/validate.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + package db import ( From f7438e4226f94a0a239e243cc3b40e5310f6b8d8 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 16 Sep 2019 14:19:53 -0800 Subject: [PATCH 053/124] docs(CONTRIBUTING): Create CONTRIBUTING.md for new developers --- CONTRIBUTING.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..98a951e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,63 @@ +# CONTRIBUTING + +Thank you for your interest in contributing to the Factom Asset Tokens Daemon +project. There is [plenty of +work](https://github.com/Factom-Asset-Tokens/fatd/projects/2) to do and we +welcome your contribution. + +### Ways to contribute +- Open an issue: + - Report a bug + - Request a feature + - Suggesting an improvement +- Debug an open issue: + - Reproduce and confirm it + - Identify the issue in code + - Suggest a fix +- Open a pull request: + - Implement a bug fix + - Implement an approved feature, + - Update documentation or code not conforming to [policy](./CODE_POLICY.md) + +## Getting started + +Aside from getting your build environment set up, which is described in the +[README](./README.md), please take the time to read through this and the [Code +Policy](./CODE_POLICY.md). These documents are going to evolve over time, so +please take special note of any changes to these files when you pull. + +Check out the [project +board](https://github.com/Factom-Asset-Tokens/fatd/projects/2) to get an idea +about what is currently being worked on. + +## Reporting Issues + +**!!!DO NOT REPORT SECURITY ISSUES IN THE PUBLIC ISSUE TRACKER!!!** + +Security related issues should be emailed directly to +[adam@canonical-ledgers.com](mailto:adam@canonical-ledgers.com). + +**!!!DO NOT REPORT SECURITY ISSUES IN THE PUBLIC ISSUE TRACKER!!!** + +When reporting issues, please try to use the templates provided by the GitHub +Issue tracker, which can also be found in `.github/ISSUE_TEMPLATE/` on the +`master` branch. + +Please do not use the issue tracker for support. Use the [Factom Asset Tokens +Discord](https://discord.gg/wGqT8VB) server instead. We'll be happy to help. + + +## Opening Pull Requests + +**!!!DO NOT DISCLOSE SECURITY ISSUES OR SUBMIT PATCHES HERE!!!** + +Please do not submit PRs that address unpublished security issues. If the issue +is security related, copy this template and email the issue or patch to +[adam@canonical-ledgers.com](mailto:adam@canonical-ledgers.com). + +**!!!DO NOT DISCLOSE SECURITY ISSUES OR SUBMIT PATCHES HERE!!!** + +It is highly recommended to wait for confirmation from @AdamSLevy before +proceeding with a major code change. However, if the change is small and is +clearly in line with an open issue or corrects something, by all means please +go ahead and open a PR. From 895d2e278890e92a088c2bb0023dafdfec78705d Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 16 Sep 2019 15:30:14 -0800 Subject: [PATCH 054/124] docs(CODE_POLICY): Add a code policy --- CODE_POLICY.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 CODE_POLICY.md diff --git a/CODE_POLICY.md b/CODE_POLICY.md new file mode 100644 index 0000000..f6c60c7 --- /dev/null +++ b/CODE_POLICY.md @@ -0,0 +1,111 @@ +# CODE POLICY + +## Core Values + +The following policies are oriented to promote the following: +1. Correctness +2. Consistency +3. Simplicity +4. Performance + +Note that performance is dead last. We address performance issues only when +they arise, and never before they are actually a problem for someone. +Optimizing for performance generally means increasing complexity, which is a +trade off that must be weighed as part of design. + +It is much better to have a simple design that is easy for anyone to debug, +than a performant design that is difficult to debug. It is better to bear the +keystroke cost of clear code upfront, than bear the cost of increased time +spent debugging later. + +> DONT MAKE THINGS EASY TO DO, MAKE THEM EASY TO UNDERSTAND. +> +> -- [Bill Kenedy, Ardan Labs](https://twitter.com/goinggodotnet) + +## Git + +### Committing +Do the following before committing: +- Ensure the code builds using `make`. +- Always run `go mod tidy` before committing your code and include changes to + `go.mod` and `go.sum` to ensure reproducible builds. + +### Commit messages +Commit messages allow for developers to quickly and concisely review changes. +Bad commit messages force developers to look at the code and wonder why a +change was made. + +Commit messages should follow these conventions: +1. [Separate subject from body with a blank line](https://chris.beams.io/posts/git-commit/#separate) +2. [Limit the subject line to 50 characters (as best as possible)](https://chris.beams.io/posts/git-commit/#limit-50) +3. [Capitalize the subject line](https://chris.beams.io/posts/git-commit/#capitalize) +4. [Do not end the subject line with a period](https://chris.beams.io/posts/git-commit/#end) +5. [Use the imperative mood in the subject line](https://chris.beams.io/posts/git-commit/#imperative) +6. [Wrap the body at 72 characters](https://chris.beams.io/posts/git-commit/#wrap-72) +7. [Use the body to explain what and why vs. how](https://chris.beams.io/posts/git-commit/#why-not-how) + +Most of those are self explanatory but it is recommended that everyone at least +review the links for 5 and 7. + +### Branches +We generally follow use a [Git +Flow](https://nvie.com/posts/a-successful-git-branching-model/) branching +model. The two major long running branches are: +- `develop` - where the action happens, you probably want to start from here +- `master` - latest official release, you'll only need this when tracking down + an open bug on the release + +These two branches shall never be rebased or `--force` pushed. + +Other transient branches shall use the following naming conventions +- `feature/ABC` - long running features under development, generally "owned" by + one developer and only pushed to share for code reviews, regularly rebased on +`develop` and later deleted +- `release/vX.X.X` - where the next release is prepared and reviewed, based on + `develop`, deleted after merging into `master` and back into `develop` +- `hotfix/DEF` - where a bugfix for the current release is prepared and + reviewed, based on `master`, deleted after merging back into `master` and +into `develop` + +#### Rebase onto develop/master, don't merge +When working on your own fork, please always rebase onto whatever branch you +intend to make your PR against, and never merge from the base branch (`master` +or `develop`) to your feature. + +## Golang + +### Imports and modules + +Importing external code should be done with care. Imports should be evaluated +on test coverage, recent development activity, documentation, and code quality. +All external code that is called should be read and understood. + +Always run `go mod tidy` before committing your code. + +### Packages + +Packages should *provide* something useful and specific, not just contain +things. Common or util packages and the like are prohibited. + + +### Types + +#### Pointer/Value semantics + + +### Goroutines + +You probably don't need to use a goroutine to solve the problem. Always write a +serialized solution first, evaluate performance, and only then can a concurrent +solution be considered. + + +### Logging + + +### Documentation + + +### Testing + + From 1b0903b46b855cf9c0dacdbfe80d37e09ae0bd02 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 16 Sep 2019 16:15:57 -0800 Subject: [PATCH 055/124] docs(CODE_POLICY): Add recommended reading, other golang details --- CODE_POLICY.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/CODE_POLICY.md b/CODE_POLICY.md index f6c60c7..a7f668d 100644 --- a/CODE_POLICY.md +++ b/CODE_POLICY.md @@ -22,6 +22,17 @@ spent debugging later. > > -- [Bill Kenedy, Ardan Labs](https://twitter.com/goinggodotnet) +## Recommended Reading + +- [SOLID Go Design by Dave Cheney](https://dave.cheney.net/2016/08/20/solid-go-design) +- [Data and Semantics by Bill Kenedy](https://www.ardanlabs.com/blog/2017/06/design-philosophy-on-data-and-semantics.html) +- [For Range Semantics by Bill Kenedy](https://www.ardanlabs.com/blog/2017/06/for-range-semantics.html) +- [Interface Values are Valueless by Bill Kenedy](https://www.ardanlabs.com/blog/2018/03/interface-values-are-valueless.html) +- [On Packaging by Bill Kenedy](https://www.ardanlabs.com/blog/2017/02/design-philosophy-on-packaging.html) +- [On Logging by Bill Kenedy](https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html) +- [Ardan Labs Go Training repo](https://github.com/ardanlabs/gotraining) +- [Go and SQLite by David Crawshaw](https://crawshaw.io/blog/go-and-sqlite) + ## Git ### Committing @@ -85,11 +96,41 @@ Always run `go mod tidy` before committing your code. ### Packages Packages should *provide* something useful and specific, not just contain -things. Common or util packages and the like are prohibited. +things. Packages named `common`, `utils`, and the like are prohibited. + +### Variables + +When intentionally declaring a variable with its zero value, use the `var` +declaration syntax. +```golang +var x int +``` + +Only use the short declaration syntax `:=` when declaring AND initializing a +variable to a non-zero value. +```golang +y := Type{Hello: "World"} +x, err := computeX() +``` +Never do this: `x := Type{}` + +### Errors +Errors must always be checked, even if it is extremely unlikely, or guaranteed +not to occur. Most errors should cause `fatd` to cleanly exit. Only a few +specific errors should ### Types +#### Interfaces +Interfaces define behavior, not data. Do not use interfaces to represent data. +Interfaces should describe what something *does*, not what it *is*. + +As a general rule, you probably don't need an interface. Create the concrete +type first and *discover* the appropriate interfaces later when you refactor to +de-duplicate code that needs to *do* the same thing to more than one type. + + #### Pointer/Value semantics From cdc579c66b3081a9da2dd241cad80abbaaed7c82 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 16 Sep 2019 16:37:55 -0800 Subject: [PATCH 056/124] docs(POLICY): Start logging policy --- CODE_POLICY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CODE_POLICY.md b/CODE_POLICY.md index a7f668d..f70b20c 100644 --- a/CODE_POLICY.md +++ b/CODE_POLICY.md @@ -133,6 +133,19 @@ de-duplicate code that needs to *do* the same thing to more than one type. #### Pointer/Value semantics +#### Factory functions +Factory functions construct and initialize a type and by convention start with +the word `New`. + +Only create factory functions for types where the initialization/set-up is not +possible or obvious from outside the package. + +Do not simply create factory functions out of convenience. It is preferred that +types that can be initialized by the user, are left to the user to initialize. + +Factory functions must follow the data semantics of the type. See Pointer/Value +semantics above. + ### Goroutines @@ -143,6 +156,9 @@ solution be considered. ### Logging +Only main, engine, and srv get to log. No other package may log. Other packages +must return errors up to the caller. + ### Documentation From 9a09edd2f8f04911a81d8c425ca859c6bc7e0078 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 18 Sep 2019 15:45:59 -0800 Subject: [PATCH 057/124] refactor(fat1,2): Use fat.TypeFATX for fatX.Type --- fat/fat0/fat0.go | 27 --------------------------- fat/fat0/fat0_test.go | 35 ----------------------------------- fat/fat0/transaction.go | 2 ++ fat/fat1/fat1.go | 27 --------------------------- fat/fat1/fat1_test.go | 35 ----------------------------------- fat/fat1/transaction.go | 2 ++ 6 files changed, 4 insertions(+), 124 deletions(-) delete mode 100644 fat/fat0/fat0.go delete mode 100644 fat/fat0/fat0_test.go delete mode 100644 fat/fat1/fat1.go delete mode 100644 fat/fat1/fat1_test.go diff --git a/fat/fat0/fat0.go b/fat/fat0/fat0.go deleted file mode 100644 index b21e2cb..0000000 --- a/fat/fat0/fat0.go +++ /dev/null @@ -1,27 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat0 - -import "github.com/Factom-Asset-Tokens/fatd/fat" - -const Type = fat.Type(0) diff --git a/fat/fat0/fat0_test.go b/fat/fat0/fat0_test.go deleted file mode 100644 index 775f1e0..0000000 --- a/fat/fat0/fat0_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat0_test - -import ( - "testing" - - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/stretchr/testify/assert" -) - -func TestType(t *testing.T) { - assert.Equal(t, fat.TypeFAT0, fat0.Type) -} diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index b3d3533..474d7f5 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -31,6 +31,8 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) +const Type = fat.TypeFAT0 + // Transaction represents a fat0 transaction, which can be a normal account // transaction or a coinbase transaction depending on the Inputs and the // RCD/signature pair. diff --git a/fat/fat1/fat1.go b/fat/fat1/fat1.go deleted file mode 100644 index df32e5c..0000000 --- a/fat/fat1/fat1.go +++ /dev/null @@ -1,27 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat1 - -import "github.com/Factom-Asset-Tokens/fatd/fat" - -const Type = fat.Type(1) diff --git a/fat/fat1/fat1_test.go b/fat/fat1/fat1_test.go deleted file mode 100644 index 98e516f..0000000 --- a/fat/fat1/fat1_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat1_test - -import ( - "testing" - - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/stretchr/testify/assert" -) - -func TestType(t *testing.T) { - assert.Equal(t, fat.TypeFAT1, fat1.Type) -} diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index ec77333..26f90e8 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -31,6 +31,8 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" ) +const Type = fat.TypeFAT1 + // Transaction represents a fat1 transaction, which can be a normal account // transaction or a coinbase transaction depending on the Inputs and the // RCD/signature pair. From 707386d661e418f8fc3cbdf6e3de8e7f2098b0c0 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 18 Sep 2019 18:43:48 -0800 Subject: [PATCH 058/124] refactor(db): Abstract entries and eblocks to their own packages Now the database functions for working with the entries and eblocks tables have been moved to their own packages for easier reuse as new FAT chain types are added. --- cli/cmd/gettransactions.go | 6 +- db/apply.go | 10 ++- db/{eblock.go => eblocks/eblocks.go} | 57 +++++++----- db/{entry.go => entries/entries.go} | 116 +++++++++++++++--------- db/metadata.go | 8 +- db/nftoken.go | 9 +- db/schema.go | 30 +------ db/sql.go | 102 --------------------- db/sqlbuilder/sqlbuilder.go | 129 +++++++++++++++++++++++++++ db/validate.go | 14 +-- srv/methods.go | 11 +-- srv/params.go | 8 +- 12 files changed, 282 insertions(+), 218 deletions(-) rename db/{eblock.go => eblocks/eblocks.go} (69%) rename db/{entry.go => entries/entries.go} (58%) delete mode 100644 db/sql.go create mode 100644 db/sqlbuilder/sqlbuilder.go diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index 7eca104..72e2b56 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -41,7 +41,7 @@ var ( StartHash: new(factom.Bytes32), NFTokenID: new(fat1.NFTokenID), ParamsToken: srv.ParamsToken{ChainID: paramsToken.ChainID}, - ParamsPagination: srv.ParamsPagination{Page: new(uint64), + ParamsPagination: srv.ParamsPagination{Page: new(uint), Order: "desc"}, } to, from bool @@ -83,8 +83,8 @@ more, and in the case of a FAT-1 chain, by a single --nftokenid. Use --page and rootCmplCmd.Sub["help"].Sub["get"].Sub["transactions"] = complete.Command{} flags := cmd.Flags() - flags.Uint64VarP(paramsGetTxs.Page, "page", "p", 1, "Page of returned txs") - flags.Uint64VarP(¶msGetTxs.Limit, "limit", "l", 10, "Limit of returned txs") + flags.UintVarP(paramsGetTxs.Page, "page", "p", 1, "Page of returned txs") + flags.UintVarP(¶msGetTxs.Limit, "limit", "l", 10, "Limit of returned txs") flags.VarPF((*txOrder)(¶msGetTxs.Order), "order", "", "Order of returned txs"). DefValue = "desc" flags.BoolVar(&to, "to", false, "Request only txs TO the given --address set") diff --git a/db/apply.go b/db/apply.go index 7b52213..2b08497 100644 --- a/db/apply.go +++ b/db/apply.go @@ -27,6 +27,8 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" @@ -47,7 +49,7 @@ func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) chain.Head = eb // Insert latest EBlock. - if err = chain.insertEBlock(eb, dbKeyMR); err != nil { + if err = eblocks.Insert(chain.Conn, eb, dbKeyMR); err != nil { return } @@ -61,7 +63,7 @@ func (chain *Chain) Apply(dbKeyMR *factom.Bytes32, eb factom.EBlock) (err error) } func (chain *Chain) ApplyEntry(e factom.Entry) (txErr, err error) { - ei, err := chain.InsertEntry(e, chain.Head.Sequence) + ei, err := entries.Insert(chain.Conn, e, chain.Head.Sequence) if err != nil { return } @@ -163,7 +165,7 @@ func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { return } e := tx.FactomEntry() - valid, err := CheckEntryUniquelyValid(chain.Conn, ei, e.Hash) + valid, err := entries.CheckUniquelyValid(chain.Conn, ei, e.Hash) if err != nil { return } @@ -172,7 +174,7 @@ func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { return } - if err = chain.setEntryValid(ei); err != nil { + if err = entries.SetValid(chain.Conn, ei); err != nil { return } return diff --git a/db/eblock.go b/db/eblocks/eblocks.go similarity index 69% rename from db/eblock.go rename to db/eblocks/eblocks.go index 10b6362..a040e73 100644 --- a/db/eblock.go +++ b/db/eblocks/eblocks.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package db +package eblocks import ( "fmt" @@ -30,9 +30,21 @@ import ( "github.com/Factom-Asset-Tokens/factom" ) -func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) error { +// CreateTable is a SQL string that creates the "eblocks" table. +const CreateTable = `CREATE TABLE "eblocks" ( + "seq" INTEGER PRIMARY KEY, + "key_mr" BLOB NOT NULL UNIQUE, + "db_height" INTEGER NOT NULL UNIQUE, + "db_key_mr" BLOB NOT NULL UNIQUE, + "timestamp" INTEGER NOT NULL, + "data" BLOB NOT NULL +); +` + +// Insert eb into the "eblocks" table on conn with dbKeyMR. +func Insert(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) error { // Ensure that this is the next EBlock. - prevKeyMR, err := SelectKeyMR(chain.Conn, eb.Sequence-1) + prevKeyMR, err := SelectKeyMR(conn, eb.Sequence-1) if *eb.PrevKeyMR != prevKeyMR { return fmt.Errorf("invalid EBlock{}.PrevKeyMR") } @@ -42,7 +54,7 @@ func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) erro if err != nil { panic(fmt.Errorf("factom.EBlock{}.MarshalBinary(): %v", err)) } - stmt := chain.Conn.Prep(`INSERT INTO "eblocks" + stmt := conn.Prep(`INSERT INTO "eblocks" ("seq", "key_mr", "db_height", "db_key_mr", "timestamp", "data") VALUES (?, ?, ?, ?, ?, ?);`) stmt.BindInt64(1, int64(eb.Sequence)) @@ -56,16 +68,14 @@ func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) erro return err } -// SelectEBlockWhere is a SQL fragment that must be appended with the condition -// of a WHERE clause and a final semi-colon. -const SelectEBlockWhere = `SELECT "key_mr", "data", "timestamp" FROM "eblocks" WHERE ` +// SelectWhere is a SQL fragment for retrieving rows from the "eblocks" table +// with Select(). +const SelectWhere = `SELECT "key_mr", "data", "timestamp" FROM "eblocks" WHERE ` -// SelectEBlock uses stmt to populate and return a new factom.EBlock. Since -// column position is used to address the data, the stmt must start with -// `SELECT "key_mr", "data", "timestamp"`. This can be called repeatedly until -// stmt.Step() returns false, in which case the returned factom.EBlock will not -// be populated. -func SelectEBlock(stmt *sqlite.Stmt) (factom.EBlock, error) { +// Select the next factom.EBlock from the given prepared Stmt. +// +// The Stmt must be created with a SQL string starting with SelectWhere. +func Select(stmt *sqlite.Stmt) (factom.EBlock, error) { var eb factom.EBlock hasRow, err := stmt.Step() if err != nil { @@ -92,20 +102,23 @@ func SelectEBlock(stmt *sqlite.Stmt) (factom.EBlock, error) { return eb, nil } -func SelectEBlockByHeight(conn *sqlite.Conn, height uint32) (factom.EBlock, error) { - stmt := conn.Prep(SelectEBlockWhere + `"db_height" = ?;`) +// SelectByHeight returns the factom.EBlock with the given height. +func SelectByHeight(conn *sqlite.Conn, height uint32) (factom.EBlock, error) { + stmt := conn.Prep(SelectWhere + `"db_height" = ?;`) stmt.BindInt64(1, int64(height)) defer stmt.Reset() - return SelectEBlock(stmt) + return Select(stmt) } -func SelectEBlockBySequence(conn *sqlite.Conn, seq uint32) (factom.EBlock, error) { - stmt := conn.Prep(SelectEBlockWhere + `"seq" = ?;`) +// SelectBySequence returns the factom.EBlock with sequence seq. +func SelectBySequence(conn *sqlite.Conn, seq uint32) (factom.EBlock, error) { + stmt := conn.Prep(SelectWhere + `"seq" = ?;`) stmt.BindInt64(1, int64(seq)) defer stmt.Reset() - return SelectEBlock(stmt) + return Select(stmt) } +// SelectKeyMR returns the KeyMR for the EBlock with sequence seq. func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { var keyMR factom.Bytes32 stmt := conn.Prep(`SELECT "key_mr" FROM "eblocks" WHERE "seq" = ?;`) @@ -126,6 +139,7 @@ func SelectKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { return keyMR, nil } +// SelectDBKeyMR returns the DBKeyMR for the EBlock with sequence seq. func SelectDBKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { var dbKeyMR factom.Bytes32 stmt := conn.Prep(`SELECT "db_key_mr" FROM "eblocks" WHERE "seq" = ?;`) @@ -146,12 +160,13 @@ func SelectDBKeyMR(conn *sqlite.Conn, seq uint32) (factom.Bytes32, error) { return dbKeyMR, nil } -func SelectEBlockLatest(conn *sqlite.Conn) (factom.EBlock, factom.Bytes32, error) { +// SelectLatest returns the most recent factom.EBlock. +func SelectLatest(conn *sqlite.Conn) (factom.EBlock, factom.Bytes32, error) { var dbKeyMR factom.Bytes32 stmt := conn.Prep( `SELECT "key_mr", "data", "timestamp", "db_key_mr" FROM "eblocks" WHERE "seq" = (SELECT max("seq") FROM "eblocks");`) - eb, err := SelectEBlock(stmt) + eb, err := Select(stmt) defer stmt.Reset() if err != nil { return eb, dbKeyMR, err diff --git a/db/entry.go b/db/entries/entries.go similarity index 58% rename from db/entry.go rename to db/entries/entries.go index 9c443c2..b69dc92 100644 --- a/db/entry.go +++ b/db/entries/entries.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package db +package entries import ( "fmt" @@ -30,16 +30,33 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/sqlbuilder" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -func (chain *Chain) InsertEntry(e factom.Entry, ebSeq uint32) (int64, error) { +// CreateTable is a SQL string that creates the "entries" table. +const CreateTable = `CREATE TABLE "entries" ( + "id" INTEGER PRIMARY KEY, + "eb_seq" INTEGER NOT NULL, + "timestamp" INTEGER NOT NULL, + "valid" BOOL NOT NULL DEFAULT FALSE, + "hash" BLOB NOT NULL, + "data" BLOB NOT NULL, + + FOREIGN KEY("eb_seq") REFERENCES "eblocks" +); +CREATE INDEX "idx_entries_eb_seq" ON "entries"("eb_seq"); +CREATE INDEX "idx_entries_hash" ON "entries"("hash"); +` + +// Insert e into the "entries" table on conn with the EBlock reference ebSeq. +func Insert(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) { data, err := e.MarshalBinary() if err != nil { panic(fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err)) } - stmt := chain.Conn.Prep(`INSERT INTO "entries" + stmt := conn.Prep(`INSERT INTO "entries" ("eb_seq", "timestamp", "hash", "data") VALUES (?, ?, ?, ?);`) stmt.BindInt64(1, int64(int32(ebSeq))) // Preserve uint32(-1) as -1 @@ -50,25 +67,31 @@ func (chain *Chain) InsertEntry(e factom.Entry, ebSeq uint32) (int64, error) { if _, err := stmt.Step(); err != nil { return -1, err } - return chain.Conn.LastInsertRowID(), nil + return conn.LastInsertRowID(), nil } -func (chain *Chain) setEntryValid(id int64) error { - stmt := chain.Conn.Prep(`UPDATE "entries" SET "valid" = 1 WHERE "id" = ?;`) +// SetValid marks the entry valid at the id'th row of the "entries" table. +func SetValid(conn *sqlite.Conn, id int64) error { + stmt := conn.Prep(`UPDATE "entries" SET "valid" = 1 WHERE "id" = ?;`) stmt.BindInt64(1, id) _, err := stmt.Step() if err != nil { return err } - if chain.Conn.Changes() == 0 { + if conn.Changes() == 0 { panic("no entries updated") } return nil } -const SelectEntryWhere = `SELECT "hash", "data", "timestamp" FROM "entries" WHERE ` +// SelectWhere is a SQL fragment for retrieving rows from the "entries" table +// with Select(). +const SelectWhere = `SELECT "hash", "data", "timestamp" FROM "entries" WHERE ` -func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { +// Select the next factom.Entry from the given prepared Stmt. +// +// The Stmt must be created with a SQL string starting with SelectWhere. +func Select(stmt *sqlite.Stmt) (factom.Entry, error) { var e factom.Entry hasRow, err := stmt.Step() if err != nil { @@ -95,42 +118,52 @@ func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { return e, nil } -func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + `"id" = ?;`) +// SelectByID returns the factom.Entry at row id. +func SelectByID(conn *sqlite.Conn, id int64) (factom.Entry, error) { + stmt := conn.Prep(SelectWhere + `"id" = ?;`) stmt.BindInt64(1, id) defer stmt.Reset() - return SelectEntry(stmt) + return Select(stmt) } -func SelectEntryByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + `"hash" = ?;`) +// SelectByHash returns the first factom.Entry with hash. +func SelectByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { + stmt := conn.Prep(SelectWhere + `"hash" = ?;`) stmt.BindBytes(1, hash[:]) defer stmt.Reset() - return SelectEntry(stmt) + return Select(stmt) } -func SelectEntryByHashValid(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + `"hash" = ? AND "valid" = true;`) +// SelectValidByHash returns the first valid factom.Entry with hash. +func SelectValidByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { + stmt := conn.Prep(SelectWhere + `"hash" = ? AND "valid" = true;`) stmt.BindBytes(1, hash[:]) defer stmt.Reset() - return SelectEntry(stmt) + return Select(stmt) } -func SelectEntryCount(conn *sqlite.Conn, validOnly bool) (int64, error) { +// SelectCount returns the total number of rows in the "entries" table. If +// validOnly is true, only the rows where "valid" = true are counted. +func SelectCount(conn *sqlite.Conn, validOnly bool) (int64, error) { stmt := conn.Prep(`SELECT count(*) FROM "entries" WHERE (? OR "valid" = true);`) stmt.BindBool(1, !validOnly) return sqlitex.ResultInt64(stmt) } -func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, +// SelectByAddress returns the all factom.Entry where adrs and nfTkns were +// involved in the valid transaction. +// +// TODO: This should probably be moved out of the entries package and into a db +// package that is more specific to FAT0 and FAT1. +func SelectByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, adrs []factom.FAAddress, nfTkns fat1.NFTokens, toFrom, order string, - page, limit uint64) ([]factom.Entry, error) { + page, limit uint) ([]factom.Entry, error) { if page == 0 { return nil, fmt.Errorf("invalid page") } - var sql sql - sql.Append(SelectEntryWhere + `"valid" = true`) + var sql sqlbuilder.SQLBuilder + sql.Append(SelectWhere + `"valid" = true`) if startHash != nil { sql.Append(` AND "id" >= (SELECT "id" FROM "entries" WHERE "hash" = ?)`, func(s *sqlite.Stmt, p int) int { @@ -147,10 +180,10 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, panic(fmt.Errorf("invalid toFrom: %v", toFrom)) } if len(nfTkns) > 0 { - sql.Append(` AND "id" IN ( + sql.WriteString(` AND "id" IN ( SELECT "entry_id" FROM "nf_token_address_transactions" WHERE "nf_tkn_id" IN (`) // 2 open ( - sql.Bind(len(nfTkns), func(s *sqlite.Stmt, p int) int { + sql.BindNParams(len(nfTkns), func(s *sqlite.Stmt, p int) int { i := 0 for nfTkn := range nfTkns { s.BindInt64(p+i, int64(nfTkn)) @@ -158,18 +191,18 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, } return len(nfTkns) }) - sql.Append(`)`) // 1 open ( + sql.WriteString(`)`) // 1 open ( if len(adrs) > 0 { - sql.Append(` AND "address_id" IN ( + sql.WriteString(` AND "address_id" IN ( SELECT "id" FROM "addresses" WHERE "address" IN (`) // 3 open ( - sql.Bind(len(adrs), func(s *sqlite.Stmt, p int) int { + sql.BindNParams(len(adrs), func(s *sqlite.Stmt, p int) int { for i, adr := range adrs { s.BindBytes(p+i, adr[:]) } return len(adrs) }) - sql.Append(`))`) // 1 open ( + sql.WriteString(`))`) // 1 open ( } if len(toFrom) > 0 { sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, p int) int { @@ -177,38 +210,38 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, return 1 }) } - sql.Append(`)`) // 0 open { + sql.WriteString(`)`) // 0 open { } else if len(adrs) > 0 { - sql.Append(` AND "id" IN ( + sql.WriteString(` AND "id" IN ( SELECT "entry_id" FROM "address_transactions" WHERE "address_id" IN ( SELECT "id" FROM "addresses" WHERE "address" IN (`) // 3 open ( - sql.Bind(len(adrs), func(s *sqlite.Stmt, p int) int { + sql.BindNParams(len(adrs), func(s *sqlite.Stmt, p int) int { for i, adr := range adrs { s.BindBytes(p+i, adr[:]) } return len(adrs) }) - sql.Append(`))`) // 1 open ( + sql.WriteString(`))`) // 1 open ( if len(toFrom) > 0 { sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, p int) int { s.BindBool(p, to) return 1 }) } - sql.Append(`)`) // 0 open ( + sql.WriteString(`)`) // 0 open ( } - sql.OrderPaginate(order, page, limit) + sql.OrderByPaginate("id", order, page, limit) stmt := sql.Prep(conn) defer stmt.Reset() var entries []factom.Entry for { - e, err := SelectEntry(stmt) + e, err := Select(stmt) if err != nil { return nil, err } @@ -221,7 +254,9 @@ func SelectEntryByAddress(conn *sqlite.Conn, startHash *factom.Bytes32, return entries, nil } -func CheckEntryUniquelyValid(conn *sqlite.Conn, +// CheckUniquelyValid returns true if there are no valid entries earlier than +// id that have the same hash. If id is 0, then all entries are checked. +func CheckUniquelyValid(conn *sqlite.Conn, id int64, hash *factom.Bytes32) (bool, error) { stmt := conn.Prep(`SELECT count(*) FROM "entries" WHERE "valid" = true AND (? OR "id" < ?) AND "hash" = ?;`) @@ -235,10 +270,11 @@ func CheckEntryUniquelyValid(conn *sqlite.Conn, return val == 0, nil } -func SelectEntryLatestValid(conn *sqlite.Conn) (factom.Entry, error) { - stmt := conn.Prep(SelectEntryWhere + +// SelectLatestValid returns the most recent valid factom.Entry. +func SelectLatestValid(conn *sqlite.Conn) (factom.Entry, error) { + stmt := conn.Prep(SelectWhere + `"id" = (SELECT max("id") FROM "entries" WHERE "valid" = true);`) - e, err := SelectEntry(stmt) + e, err := Select(stmt) defer stmt.Reset() if err != nil { return e, err diff --git a/db/metadata.go b/db/metadata.go index b5f4719..b7d6d39 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -27,6 +27,8 @@ import ( "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" ) @@ -96,7 +98,7 @@ func (chain *Chain) numIssuedAdd(add uint64) error { func (chain *Chain) loadMetadata() error { defer chain.setApplyFunc() // Load NameIDs - first, err := SelectEntryByID(chain.Conn, 1) + first, err := entries.SelectByID(chain.Conn, 1) if err != nil { return err } @@ -111,7 +113,7 @@ func (chain *Chain) loadMetadata() error { chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) // Load Chain Head - eb, dbKeyMR, err := SelectEBlockLatest(chain.Conn) + eb, dbKeyMR, err := eblocks.SelectLatest(chain.Conn) if err != nil { return err } @@ -168,7 +170,7 @@ func (chain *Chain) loadMetadata() error { return nil } initEntryID := stmt.ColumnInt64(5) - chain.Issuance.Entry.Entry, err = SelectEntryByID(chain.Conn, initEntryID) + chain.Issuance.Entry.Entry, err = entries.SelectByID(chain.Conn, initEntryID) if err != nil { return err } diff --git a/db/nftoken.go b/db/nftoken.go index 2a2d6ce..d4878b4 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -29,6 +29,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/sqlbuilder" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) @@ -119,7 +120,7 @@ func SelectNFToken(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (factom.FAAddress, return owner, creationHash, metadata, nil } -func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint64) ([]fat1.NFTokenID, +func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint) ([]fat1.NFTokenID, []factom.FAAddress, []factom.Bytes32, [][]byte, error) { if page == 0 { return nil, nil, nil, nil, fmt.Errorf("invalid page") @@ -164,18 +165,18 @@ func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint64) ([]fat1 } func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, - page, limit uint64, order string) (fat1.NFTokens, error) { + page, limit uint, order string) (fat1.NFTokens, error) { if page == 0 { return nil, fmt.Errorf("invalid page") } - var sql sql + var sql sqlbuilder.SQLBuilder sql.Append(`SELECT "id" FROM "nf_tokens" WHERE "owner_id" = ( SELECT "id" FROM "addresses" WHERE "address" = ?)`, func(s *sqlite.Stmt, c int) int { s.BindBytes(c, adr[:]) return 1 }) - sql.OrderPaginate(order, page, limit) + sql.OrderByPaginate("id", order, page, limit) stmt := sql.Prep(conn) defer stmt.Reset() diff --git a/db/schema.go b/db/schema.go index d50ff9d..cc26275 100644 --- a/db/schema.go +++ b/db/schema.go @@ -27,33 +27,11 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" + "github.com/Factom-Asset-Tokens/fatd/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/db/entries" ) const ( - createTableEBlocks = `CREATE TABLE "eblocks" ( - "seq" INTEGER PRIMARY KEY, - "key_mr" BLOB NOT NULL UNIQUE, - "db_height" INTEGER NOT NULL UNIQUE, - "db_key_mr" BLOB NOT NULL UNIQUE, - "timestamp" INTEGER NOT NULL, - "data" BLOB NOT NULL -); -` - - createTableEntries = `CREATE TABLE "entries" ( - "id" INTEGER PRIMARY KEY, - "eb_seq" INTEGER NOT NULL, - "timestamp" INTEGER NOT NULL, - "valid" BOOL NOT NULL DEFAULT FALSE, - "hash" BLOB NOT NULL, - "data" BLOB NOT NULL, - - FOREIGN KEY("eb_seq") REFERENCES "eblocks" -); -CREATE INDEX "idx_entries_eb_seq" ON "entries"("eb_seq"); -CREATE INDEX "idx_entries_hash" ON "entries"("hash"); -` - createTableAddresses = `CREATE TABLE "addresses" ( "id" INTEGER PRIMARY KEY, "address" BLOB NOT NULL UNIQUE, @@ -133,8 +111,8 @@ CREATE VIEW "nf_token_address_transactions" AS // For the sake of simplicity, all chain DBs use the exact same schema, // regardless of whether they actually make use of the NFTokens tables. - chainDBSchema = createTableEBlocks + - createTableEntries + + chainDBSchema = eblocks.CreateTable + + entries.CreateTable + createTableAddresses + createTableAddressTransactions + createTableNFTokens + diff --git a/db/sql.go b/db/sql.go deleted file mode 100644 index 6a934c1..0000000 --- a/db/sql.go +++ /dev/null @@ -1,102 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package db - -import ( - "fmt" - "strings" - - "crawshaw.io/sqlite" -) - -// bindFunc is a function that binds one or more values to a sqlite.Stmt. The -// starting param index is passed into the bindFunc when sql.Prep is called. -// The bindFunc must return the number of binds it called on the Stmt so that -// the param index can be advanced. -type bindFunc func(*sqlite.Stmt, int) int - -// sql is a SQL builder for more complex queries. It allows for adding binds to -// a sqlite.Stmt before it is prepared. As SQL is appended to the query, -// bindFuncs are queued for later when sql.Prep() is called. Do not copy a -// non-zero sql. -type sql struct { - sql strings.Builder - binds []bindFunc -} - -// Appends a trailing ';' to the SQL and calls conn.Prep. Finally all bindFuncs -// are called and the stmt is returned ready for its first Stmt.Step() call. -func (sql *sql) Prep(conn *sqlite.Conn) *sqlite.Stmt { - sql.sql.WriteString(`;`) - stmt := conn.Prep(sql.sql.String()) - param := 1 - for _, bind := range sql.binds { - param += bind(stmt, param) - } - return stmt -} - -// Append str to the SQL and append the binds. -func (sql *sql) Append(str string, binds ...bindFunc) { - sql.sql.WriteString(str) - sql.binds = append(sql.binds, binds...) -} - -// Append n comma separated params placeholders (e.g. "?, ?, ... , ?") to the -// SQL and append the binds. -func (sql *sql) Bind(n int, binds ...bindFunc) { - str := strings.TrimRight(strings.Repeat("?, ", n), ", ") - sql.Append(str, binds...) -} - -const MaxLimit = 600 - -// Append "LIMIT ?, ?" to the SQL and the appropriate page and limit binds. -func (sql *sql) Paginate(page, limit uint64) { - if limit == 0 || limit > MaxLimit { - limit = MaxLimit - } - sql.Append(` LIMIT ?, ?`, func(s *sqlite.Stmt, p int) int { - s.BindInt64(p, int64((page-1)*limit)) - s.BindInt64(p+1, int64(limit)) - return 2 - }) -} - -// Append "ORDER BY "id" ASC or DESC". No binds are added. -func (sql *sql) Order(order string) { - switch strings.ToLower(order) { - case "asc", "": - sql.Append(` ORDER BY "id" ASC`) - case "desc": - sql.Append(` ORDER BY "id" DESC`) - default: - panic(fmt.Errorf("invalid order: %v", order)) - } -} - -// Combines Order and Paginate in one call. -func (sql *sql) OrderPaginate(order string, page, limit uint64) { - sql.Order(order) - sql.Paginate(page, limit) -} diff --git a/db/sqlbuilder/sqlbuilder.go b/db/sqlbuilder/sqlbuilder.go new file mode 100644 index 0000000..60b7f36 --- /dev/null +++ b/db/sqlbuilder/sqlbuilder.go @@ -0,0 +1,129 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package sqlbuilder + +import ( + "fmt" + "strings" + + "crawshaw.io/sqlite" +) + +// BindFunc is a function that binds one or more values to a sqlite.Stmt. A +// valid BindFunc must use startParam as the first param index for any binds +// and must return the total number of parameters bound. +type BindFunc func(stmt *sqlite.Stmt, startParam int) int + +// SQLBuilder prepares a sqlite.Stmt that has variable numbers of Binds. +type SQLBuilder struct { + strings.Builder + Binds []BindFunc + + LimitMax uint // Max limit allowed by Paginate() +} + +// Prep prepares a sqlite.Stmt on conn using s.String() with a trailing `;` as +// the SQL, and then sequentially calls all s.Binds using the Stmt and the +// appropriate startParam. The Stmt is returned ready for its first Step(). +// +// If the total number of binds reported by the Binds differs from the total +// number of params reported by the Stmt, then Prep panics. +func (s *SQLBuilder) Prep(conn *sqlite.Conn) *sqlite.Stmt { + s.WriteString(`;`) + stmt := conn.Prep(s.String()) + param := 1 + for _, bind := range s.Binds { + param += bind(stmt, param) + } + if param-1 != stmt.BindParamCount() { + panic(fmt.Errorf( + "reported bind count (%v) does not match bind param count (%v)"+ + "\nSQL:%q", + s.String()+`;`, param-1, stmt.BindParamCount())) + } + + return stmt +} + +// Append sql and any associated binds. +// +// Do not include a `;` in sql. +// +// The sum of the binds return values must equal the number of params (e.g. +// "?") in sql or else s.Prep will panic. +func (s *SQLBuilder) Append(sql string, binds ...BindFunc) { + s.WriteString(sql) + s.Binds = append(s.Binds, binds...) +} + +// BindNParams appends n comma separated params placeholders (e.g. "?, ?, ... , +// ?") and append the binds. +// +// Do not include a `;` in sql. +// +// The sum of the binds return values must equal n or else s.Prep will panic. +func (s *SQLBuilder) BindNParams(n int, binds ...BindFunc) { + str := strings.TrimRight(strings.Repeat("?, ", n), ", ") + s.Append(str, binds...) +} + +// LimitMaxDefault is used in SQLBuilder.Paginate() if SQLBuilder.LimitMax +// equals 0. +var LimitMaxDefault uint = 600 + +// Paginate appends ` LIMIT ?, ?` and the appropriate page and limit binds. +func (s *SQLBuilder) Paginate(page, limit uint) { + if s.LimitMax == 0 { + s.LimitMax = LimitMaxDefault + } + if limit == 0 || limit > s.LimitMax { + limit = s.LimitMax + } + s.Append(` LIMIT ?, ?`, func(s *sqlite.Stmt, p int) int { + s.BindInt64(p, int64((page-1)*limit)) + s.BindInt64(p+1, int64(limit)) + return 2 + }) +} + +// OrderBy append fmt.Sprintf(` ORDER BY %q %s`, col, order). No binds are +// added. +func (s *SQLBuilder) OrderBy(col, ascDesc string) { + s.WriteString(col) + s.WriteString(`"`) + ascDesc = strings.ToUpper(ascDesc) + switch ascDesc { + case "ASC", "DESC": + case "": + ascDesc = "ASC" + default: + panic(fmt.Errorf("invalid order: %v", ascDesc)) + } + s.WriteString(fmt.Sprintf(` ORDER BY %q %v`, col, ascDesc)) +} + +// OrderByPaginate calls s.OrderBy() and then s.Paginate(). +func (s *SQLBuilder) OrderByPaginate(col, order string, page, limit uint) { + s.OrderBy(col, order) + s.Paginate(page, limit) +} diff --git a/db/validate.go b/db/validate.go index 0121432..5453fa4 100644 --- a/db/validate.go +++ b/db/validate.go @@ -30,6 +30,8 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/AdamSLevy/sqlitechangeset" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" ) @@ -47,7 +49,7 @@ func (chain Chain) Validate() (err error) { read := chain.Pool.Get(nil) defer chain.Pool.Put(read) write := chain.Conn - first, err := SelectEntryByID(read, 1) + first, err := entries.SelectByID(read, 1) if err != nil { return err } @@ -89,16 +91,16 @@ func (chain Chain) Validate() (err error) { chain.Issuance = fat.Issuance{} chain.setApplyFunc() - eBlockStmt := read.Prep(SelectEBlockWhere + `true;`) // SELECT all EBlocks. + eBlockStmt := read.Prep(eblocks.SelectWhere + `true;`) // SELECT all EBlocks. defer eBlockStmt.Reset() - entryStmt := read.Prep(SelectEntryWhere + `true;`) // SELECT all Entries. + entryStmt := read.Prep(entries.SelectWhere + `true;`) // SELECT all Entries. defer entryStmt.Reset() var eID int = 1 // Entry ID var sequence uint32 // EBlock Sequence var prevKeyMR, prevFullHash factom.Bytes32 for { - eb, err := SelectEBlock(eBlockStmt) + eb, err := eblocks.Select(eBlockStmt) if err != nil { return err } @@ -144,7 +146,7 @@ func (chain Chain) Validate() (err error) { prevKeyMR = keyMR for i, ebe := range eb.Entries { - e, err := SelectEntry(entryStmt) + e, err := entries.Select(entryStmt) if *e.Hash != *ebe.Hash { return fmt.Errorf("invalid Entry{%v}: broken EBlock link", @@ -173,7 +175,7 @@ func (chain Chain) Validate() (err error) { eb.Entries[i] = e eID++ } - dbKeyMR, err := SelectDBKeyMR(read, eb.Sequence) + dbKeyMR, err := eblocks.SelectDBKeyMR(read, eb.Sequence) if err != nil { return err } diff --git a/srv/methods.go b/srv/methods.go index 42b6080..fb7b917 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -31,6 +31,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/db/entries" "github.com/Factom-Asset-Tokens/fatd/engine" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" @@ -109,7 +110,7 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { } defer put() - entry, err := db.SelectEntryByHashValid(chain.Conn, params.Hash) + entry, err := entries.SelectValidByHash(chain.Conn, params.Hash) if err != nil { panic(err) } @@ -164,7 +165,7 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { if params.NFTokenID != nil { nfTkns, _ = fat1.NewNFTokens(params.NFTokenID) } - entries, err := db.SelectEntryByAddress(chain.Conn, params.StartHash, + entries, err := entries.SelectByAddress(chain.Conn, params.StartHash, params.Addresses, nfTkns, params.ToFrom, params.Order, *params.Page, params.Limit) @@ -329,8 +330,8 @@ func getStats(data json.RawMessage) interface{} { if err != nil { panic(err) } - txCount, err := db.SelectEntryCount(chain.Conn, true) - e, err := db.SelectEntryLatestValid(chain.Conn) + txCount, err := entries.SelectCount(chain.Conn, true) + e, err := entries.SelectLatestValid(chain.Conn) if err != nil { panic(err) } @@ -582,7 +583,7 @@ func applyTx(chain *engine.Chain, tx fat.Transaction) (txErr, err error) { return } e := tx.FactomEntry() - valid, err := db.CheckEntryUniquelyValid(chain.Conn, 0, e.Hash) + valid, err := entries.CheckUniquelyValid(chain.Conn, 0, e.Hash) if err != nil { return } diff --git a/srv/params.go b/srv/params.go index bde5a6b..adb2153 100644 --- a/srv/params.go +++ b/srv/params.go @@ -83,14 +83,14 @@ func (p ParamsToken) ValidChainID() *factom.Bytes32 { } type ParamsPagination struct { - Page *uint64 `json:"page,omitempty"` - Limit uint64 `json:"limit,omitempty"` - Order string `json:"order,omitempty"` + Page *uint `json:"page,omitempty"` + Limit uint `json:"limit,omitempty"` + Order string `json:"order,omitempty"` } func (p *ParamsPagination) IsValid() error { if p.Page == nil { - p.Page = new(uint64) + p.Page = new(uint) *p.Page = 1 } else if *p.Page == 0 { return jrpc.InvalidParams( From 53328d394fad71c35b83bc888ae11d69e8afc002 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 18 Sep 2019 19:15:41 -0800 Subject: [PATCH 059/124] refactor(db): Abstract addresses table to own package --- db/{address.go => addresses/addresses.go} | 56 +++++++++++++++++------ db/apply.go | 27 ++++++----- db/chain.go | 3 +- db/eblocks/eblocks.go | 4 +- db/entries/entries.go | 5 +- db/schema.go | 11 +---- srv/methods.go | 13 +++--- 7 files changed, 75 insertions(+), 44 deletions(-) rename db/{address.go => addresses/addresses.go} (61%) diff --git a/db/address.go b/db/addresses/addresses.go similarity index 61% rename from db/address.go rename to db/addresses/addresses.go index 9f75d2f..43e8f73 100644 --- a/db/address.go +++ b/db/addresses/addresses.go @@ -20,7 +20,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package db +// Package addresses provides functions and SQL framents for working with the +// "addresses" table, which stores factom.FAAddress with its balance. +package addresses import ( "fmt" @@ -30,8 +32,19 @@ import ( "github.com/Factom-Asset-Tokens/factom" ) -func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) { - stmt := chain.Conn.Prep(`INSERT INTO "addresses" +// CreateTable is a SQL string that creates the "addresses" table. +const CreateTable = `CREATE TABLE "addresses" ( + "id" INTEGER PRIMARY KEY, + "address" BLOB NOT NULL UNIQUE, + "balance" INTEGER NOT NULL + CONSTRAINT "insufficient balance" CHECK ("balance" >= 0) +); +` + +// Add adds add to the balance of adr, creating a new row in "addresses" if it +// does not exist. If successful, the row id of adr is returned. +func Add(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) (int64, error) { + stmt := conn.Prep(`INSERT INTO "addresses" ("address", "balance") VALUES (?, ?) ON CONFLICT("address") DO UPDATE SET "balance" = "balance" + "excluded"."balance";`) @@ -41,16 +54,21 @@ func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) if err != nil { return -1, err } - return SelectAddressID(chain.Conn, adr) + return SelectID(conn, adr) } -func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, error) { +// Sub subtracts sub from the balance of adr creating the row in "addresses" if +// it does not exist and sub is 0. If successful, the row id of adr is +// returned. If subtracting sub would result in a negative balance, txErr is +// not nil and starts with "insufficient balance". +func Sub(conn *sqlite.Conn, + adr *factom.FAAddress, sub uint64) (id int64, txErr, err error) { if sub == 0 { // Allow tx's with zeros to result in an INSERT. - id, err := chain.addressAdd(adr, 0) + id, err = Add(conn, adr, 0) return id, nil, err } - id, err := SelectAddressID(chain.Conn, adr) + id, err = SelectID(conn, adr) if err != nil { if err.Error() == sqlitexNoResultsErr { return id, fmt.Errorf("insufficient balance: %v", adr), nil @@ -60,7 +78,7 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, if id < 0 { return id, fmt.Errorf("insufficient balance: %v", adr), nil } - stmt := chain.Conn.Prep( + stmt := conn.Prep( `UPDATE addresses SET balance = balance - ? WHERE rowid = ?;`) stmt.BindInt64(1, int64(sub)) stmt.BindInt64(2, id) @@ -70,7 +88,7 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, } return id, nil, err } - if chain.Conn.Changes() == 0 { + if conn.Changes() == 0 { panic("no balances updated") } return id, nil, nil @@ -78,7 +96,8 @@ func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error, var sqlitexNoResultsErr = "sqlite: statement has no results" -func SelectAddressIDBalance(conn *sqlite.Conn, +// SelectIDBalance returns the row id and balance for the given adr. +func SelectIDBalance(conn *sqlite.Conn, adr *factom.FAAddress) (adrID int64, bal uint64, err error) { adrID = -1 stmt := conn.Prep(`SELECT "id", "balance" FROM "addresses" WHERE "address" = ?;`) @@ -96,22 +115,29 @@ func SelectAddressIDBalance(conn *sqlite.Conn, return } -func SelectAddressID(conn *sqlite.Conn, adr *factom.FAAddress) (int64, error) { +// SelectID returns the row id for the given adr. +func SelectID(conn *sqlite.Conn, adr *factom.FAAddress) (int64, error) { stmt := conn.Prep(`SELECT "id" FROM "addresses" WHERE "address" = ?;`) stmt.BindBytes(1, adr[:]) return sqlitex.ResultInt64(stmt) } -func SelectAddressCount(conn *sqlite.Conn, nonZeroOnly bool) (int64, error) { +// SelectCount returns the number of rows in "addresses". If nonZeroOnly is +// true, then only count the addresses with a non zero balance. +func SelectCount(conn *sqlite.Conn, nonZeroOnly bool) (int64, error) { stmt := conn.Prep(`SELECT count(*) FROM "addresses" WHERE "id" != 1 AND (? OR "balance" > 0);`) stmt.BindBool(1, !nonZeroOnly) return sqlitex.ResultInt64(stmt) } -func (chain *Chain) insertAddressTransaction( +// InsertTransaction inserts a row into "address_transactions" relating the +// adrID with the entryID with the given transaction direction, to. If +// successful, the row id for the new row in "address_transactions" is +// returned. +func InsertTransaction(conn *sqlite.Conn, adrID int64, entryID int64, to bool) (int64, error) { - stmt := chain.Conn.Prep(`INSERT INTO "address_transactions" + stmt := conn.Prep(`INSERT INTO "address_transactions" ("address_id", "entry_id", "to") VALUES (?, ?, ?)`) stmt.BindInt64(1, adrID) @@ -121,5 +147,5 @@ func (chain *Chain) insertAddressTransaction( if err != nil { return -1, err } - return chain.Conn.LastInsertRowID(), nil + return conn.LastInsertRowID(), nil } diff --git a/db/apply.go b/db/apply.go index 2b08497..de023d1 100644 --- a/db/apply.go +++ b/db/apply.go @@ -27,6 +27,7 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" @@ -198,18 +199,19 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, if err = chain.numIssuedAdd(addIssued); err != nil { return } - if _, err = chain.insertAddressTransaction(1, ei, false); err != nil { + if _, err = addresses.InsertTransaction( + chain.Conn, 1, ei, false); err != nil { return } } else { for adr, amount := range tx.Inputs { var ai int64 - ai, txErr, err = chain.addressSub(&adr, amount) + ai, txErr, err = addresses.Sub(chain.Conn, &adr, amount) if err != nil || txErr != nil { return } - if _, err = chain.insertAddressTransaction(ai, ei, - false); err != nil { + if _, err = addresses.InsertTransaction( + chain.Conn, ai, ei, false); err != nil { return } } @@ -217,11 +219,12 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, for adr, amount := range tx.Outputs { var ai int64 - ai, err = chain.addressAdd(&adr, amount) + ai, err = addresses.Add(chain.Conn, &adr, amount) if err != nil { return } - if _, err = chain.insertAddressTransaction(ai, ei, true); err != nil { + if _, err = addresses.InsertTransaction( + chain.Conn, ai, ei, true); err != nil { return } } @@ -249,7 +252,7 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, return } var adrTxID int64 - adrTxID, err = chain.insertAddressTransaction(1, ei, false) + adrTxID, err = addresses.InsertTransaction(chain.Conn, 1, ei, false) if err != nil { return } @@ -274,12 +277,14 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, } else { for adr, nfTkns := range tx.Inputs { var ai int64 - ai, txErr, err = chain.addressSub(&adr, uint64(len(nfTkns))) + ai, txErr, err = addresses.Sub( + chain.Conn, &adr, uint64(len(nfTkns))) if err != nil || txErr != nil { return } var adrTxID int64 - adrTxID, err = chain.insertAddressTransaction(ai, ei, false) + adrTxID, err = addresses.InsertTransaction( + chain.Conn, ai, ei, false) if err != nil { return } @@ -308,12 +313,12 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, for adr, nfTkns := range tx.Outputs { var ai int64 - ai, err = chain.addressAdd(&adr, uint64(len(nfTkns))) + ai, err = addresses.Add(chain.Conn, &adr, uint64(len(nfTkns))) if err != nil { return } var adrTxID int64 - adrTxID, err = chain.insertAddressTransaction(ai, ei, true) + adrTxID, err = addresses.InsertTransaction(chain.Conn, ai, ei, true) if err != nil { return } diff --git a/db/chain.go b/db/chain.go index a15c371..dee1263 100644 --- a/db/chain.go +++ b/db/chain.go @@ -34,6 +34,7 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/fat" _log "github.com/Factom-Asset-Tokens/fatd/log" ) @@ -124,7 +125,7 @@ func OpenNew(dbPath string, // Ensure that the coinbase address has rowid = 1. coinbase := fat.Coinbase() - if _, err = chain.addressAdd(&coinbase, 0); err != nil { + if _, err = addresses.Add(chain.Conn, &coinbase, 0); err != nil { return } diff --git a/db/eblocks/eblocks.go b/db/eblocks/eblocks.go index a040e73..bd6c6d0 100644 --- a/db/eblocks/eblocks.go +++ b/db/eblocks/eblocks.go @@ -20,6 +20,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. +// Package eblocks provides functions and SQL framents for working with the +// "eblocks" table, which stores factom.EBlock. package eblocks import ( @@ -41,7 +43,7 @@ const CreateTable = `CREATE TABLE "eblocks" ( ); ` -// Insert eb into the "eblocks" table on conn with dbKeyMR. +// Insert eb into the "eblocks" table with dbKeyMR. func Insert(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) error { // Ensure that this is the next EBlock. prevKeyMR, err := SelectKeyMR(conn, eb.Sequence-1) diff --git a/db/entries/entries.go b/db/entries/entries.go index b69dc92..33d77fd 100644 --- a/db/entries/entries.go +++ b/db/entries/entries.go @@ -20,6 +20,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. +// Package entries provides functions and SQL framents for working with the +// "entries" table, which stores factom.Entry with a valid flag. package entries import ( @@ -49,7 +51,8 @@ CREATE INDEX "idx_entries_eb_seq" ON "entries"("eb_seq"); CREATE INDEX "idx_entries_hash" ON "entries"("hash"); ` -// Insert e into the "entries" table on conn with the EBlock reference ebSeq. +// Insert e into the "entries" table with the EBlock reference ebSeq. If +// successful, the new row id of e is returned. func Insert(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) { data, err := e.MarshalBinary() if err != nil { diff --git a/db/schema.go b/db/schema.go index cc26275..3aa4a7d 100644 --- a/db/schema.go +++ b/db/schema.go @@ -27,19 +27,12 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" + "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/db/entries" ) const ( - createTableAddresses = `CREATE TABLE "addresses" ( - "id" INTEGER PRIMARY KEY, - "address" BLOB NOT NULL UNIQUE, - "balance" INTEGER NOT NULL - CONSTRAINT "insufficient balance" CHECK ("balance" >= 0) -); -` - createTableAddressTransactions = `CREATE TABLE "address_transactions" ( "entry_id" INTEGER NOT NULL, "address_id" INTEGER NOT NULL, @@ -113,7 +106,7 @@ CREATE VIEW "nf_token_address_transactions" AS // regardless of whether they actually make use of the NFTokens tables. chainDBSchema = eblocks.CreateTable + entries.CreateTable + - createTableAddresses + + addresses.CreateTable + createTableAddressTransactions + createTableNFTokens + createTableNFTokensTransactions + diff --git a/srv/methods.go b/srv/methods.go index fb7b917..f635305 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -31,6 +31,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/entries" "github.com/Factom-Asset-Tokens/fatd/engine" "github.com/Factom-Asset-Tokens/fatd/fat" @@ -217,7 +218,7 @@ func getBalance(data json.RawMessage) interface{} { } defer put() - _, balance, err := db.SelectAddressIDBalance(chain.Conn, params.Address) + _, balance, err := addresses.SelectIDBalance(chain.Conn, params.Address) if err != nil { panic(err) } @@ -264,7 +265,7 @@ func getBalances(data json.RawMessage) interface{} { } conn, put := chain.Get() defer put() - _, balance, err := db.SelectAddressIDBalance(conn, params.Address) + _, balance, err := addresses.SelectIDBalance(conn, params.Address) if err != nil { panic(err) } @@ -326,7 +327,7 @@ func getStats(data json.RawMessage) interface{} { } defer put() - _, burned, err := db.SelectAddressIDBalance(chain.Conn, &coinbaseRCDHash) + _, burned, err := addresses.SelectIDBalance(chain.Conn, &coinbaseRCDHash) if err != nil { panic(err) } @@ -336,7 +337,7 @@ func getStats(data json.RawMessage) interface{} { panic(err) } - nonZeroBalances, err := db.SelectAddressCount(chain.Conn, true) + nonZeroBalances, err := addresses.SelectCount(chain.Conn, true) if err != nil { panic(err) } @@ -510,7 +511,7 @@ func attemptApplyFAT0Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) // Check all input balances for adr, amount := range tx.Inputs { var bal uint64 - if _, bal, err = db.SelectAddressIDBalance( + if _, bal, err = addresses.SelectIDBalance( chain.Conn, &adr); err != nil { return } @@ -549,7 +550,7 @@ func attemptApplyFAT1Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) for adr, nfTkns := range tx.Inputs { var adrID int64 var bal uint64 - adrID, bal, err = db.SelectAddressIDBalance(chain.Conn, &adr) + adrID, bal, err = addresses.SelectIDBalance(chain.Conn, &adr) if err != nil { return } From 021e9cd6e7c8a89778a97b1dab2712d89b6e9a06 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 20 Sep 2019 14:42:43 -0800 Subject: [PATCH 060/124] refactor(db): Add db/nftokens package for "nf_tokens" table --- db/addresses/addresses.go | 23 +----- db/addresses/txrelation.go | 40 +++++++++ db/apply.go | 34 ++++---- db/entries/entries.go | 9 +- db/{nftoken.go => nftokens/nftokens.go} | 105 +++++++++++++++++------- db/nftokens/txrelation.go | 45 ++++++++++ db/schema.go | 61 +------------- srv/methods.go | 16 ++-- 8 files changed, 202 insertions(+), 131 deletions(-) create mode 100644 db/addresses/txrelation.go rename db/{nftoken.go => nftokens/nftokens.go} (56%) create mode 100644 db/nftokens/txrelation.go diff --git a/db/addresses/addresses.go b/db/addresses/addresses.go index 43e8f73..787d146 100644 --- a/db/addresses/addresses.go +++ b/db/addresses/addresses.go @@ -57,6 +57,8 @@ func Add(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) (int64, error) { return SelectID(conn, adr) } +const sqlitexNoResultsErr = "sqlite: statement has no results" + // Sub subtracts sub from the balance of adr creating the row in "addresses" if // it does not exist and sub is 0. If successful, the row id of adr is // returned. If subtracting sub would result in a negative balance, txErr is @@ -94,8 +96,6 @@ func Sub(conn *sqlite.Conn, return id, nil, nil } -var sqlitexNoResultsErr = "sqlite: statement has no results" - // SelectIDBalance returns the row id and balance for the given adr. func SelectIDBalance(conn *sqlite.Conn, adr *factom.FAAddress) (adrID int64, bal uint64, err error) { @@ -130,22 +130,3 @@ func SelectCount(conn *sqlite.Conn, nonZeroOnly bool) (int64, error) { stmt.BindBool(1, !nonZeroOnly) return sqlitex.ResultInt64(stmt) } - -// InsertTransaction inserts a row into "address_transactions" relating the -// adrID with the entryID with the given transaction direction, to. If -// successful, the row id for the new row in "address_transactions" is -// returned. -func InsertTransaction(conn *sqlite.Conn, - adrID int64, entryID int64, to bool) (int64, error) { - stmt := conn.Prep(`INSERT INTO "address_transactions" - ("address_id", "entry_id", "to") VALUES - (?, ?, ?)`) - stmt.BindInt64(1, adrID) - stmt.BindInt64(2, entryID) - stmt.BindBool(3, to) - _, err := stmt.Step() - if err != nil { - return -1, err - } - return conn.LastInsertRowID(), nil -} diff --git a/db/addresses/txrelation.go b/db/addresses/txrelation.go new file mode 100644 index 0000000..c5f36eb --- /dev/null +++ b/db/addresses/txrelation.go @@ -0,0 +1,40 @@ +package addresses + +import "crawshaw.io/sqlite" + +// CreateTableTransactions is a SQL string that creates the +// "address_transactions" table. +// +// The "address_transactions" table has a foreign key reference to the +// "addresses" and "entries" tables, which must exist first. +const CreateTableTransactions = `CREATE TABLE "address_transactions" ( + "entry_id" INTEGER NOT NULL, + "address_id" INTEGER NOT NULL, + "to" BOOL NOT NULL, + + PRIMARY KEY("entry_id", "address_id"), + + FOREIGN KEY("entry_id") REFERENCES "entries", + FOREIGN KEY("address_id") REFERENCES "addresses" +); +CREATE INDEX "idx_address_transactions_address_id" ON "address_transactions"("address_id"); +` + +// InsertTransactionRelation inserts a row into "address_transactions" relating +// the adrID with the entryID with the given transaction direction, to. If +// successful, the row id for the new row in "address_transactions" is +// returned. +func InsertTransactionRelation(conn *sqlite.Conn, + adrID int64, entryID int64, to bool) (int64, error) { + stmt := conn.Prep(`INSERT INTO "address_transactions" + ("address_id", "entry_id", "to") VALUES + (?, ?, ?)`) + stmt.BindInt64(1, adrID) + stmt.BindInt64(2, entryID) + stmt.BindBool(3, to) + _, err := stmt.Step() + if err != nil { + return -1, err + } + return conn.LastInsertRowID(), nil +} diff --git a/db/apply.go b/db/apply.go index de023d1..0396f27 100644 --- a/db/apply.go +++ b/db/apply.go @@ -30,6 +30,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/db/nftokens" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" @@ -199,7 +200,7 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, if err = chain.numIssuedAdd(addIssued); err != nil { return } - if _, err = addresses.InsertTransaction( + if _, err = addresses.InsertTransactionRelation( chain.Conn, 1, ei, false); err != nil { return } @@ -210,7 +211,7 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, if err != nil || txErr != nil { return } - if _, err = addresses.InsertTransaction( + if _, err = addresses.InsertTransactionRelation( chain.Conn, ai, ei, false); err != nil { return } @@ -223,7 +224,7 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, if err != nil { return } - if _, err = addresses.InsertTransaction( + if _, err = addresses.InsertTransactionRelation( chain.Conn, ai, ei, true); err != nil { return } @@ -252,25 +253,27 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, return } var adrTxID int64 - adrTxID, err = addresses.InsertTransaction(chain.Conn, 1, ei, false) + adrTxID, err = addresses.InsertTransactionRelation(chain.Conn, 1, ei, false) if err != nil { return } for nfID := range nfTkns { // Insert the NFToken with the coinbase address as a // placeholder for the owner. - txErr, err = chain.insertNFToken(nfID, 1, ei) + txErr, err = nftokens.Insert(chain.Conn, nfID, 1, ei) if err != nil || txErr != nil { return } - if err = chain.insertNFTokenTransaction(nfID, adrTxID); err != nil { + if err = nftokens.InsertTransactionRelation( + chain.Conn, nfID, adrTxID); err != nil { return } metadata := tx.TokenMetadata[nfID] if len(metadata) == 0 { continue } - if err = chain.setNFTokenMetadata(nfID, metadata); err != nil { + if err = nftokens.SetMetadata( + chain.Conn, nfID, metadata); err != nil { return } } @@ -283,14 +286,14 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, return } var adrTxID int64 - adrTxID, err = addresses.InsertTransaction( + adrTxID, err = addresses.InsertTransactionRelation( chain.Conn, ai, ei, false) if err != nil { return } for nfTkn := range nfTkns { var ownerID int64 - ownerID, err = SelectNFTokenOwnerID(chain.Conn, nfTkn) + ownerID, err = nftokens.SelectOwnerID(chain.Conn, nfTkn) if err != nil { return } @@ -303,8 +306,8 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, nfTkn, adr) return } - if err = chain.insertNFTokenTransaction( - nfTkn, adrTxID); err != nil { + if err = nftokens.InsertTransactionRelation( + chain.Conn, nfTkn, adrTxID); err != nil { return } } @@ -318,16 +321,17 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, return } var adrTxID int64 - adrTxID, err = addresses.InsertTransaction(chain.Conn, ai, ei, true) + adrTxID, err = addresses.InsertTransactionRelation( + chain.Conn, ai, ei, true) if err != nil { return } for nfID := range nfTkns { - if err = chain.setNFTokenOwner(nfID, ai); err != nil { + if err = nftokens.SetOwner(chain.Conn, nfID, ai); err != nil { return } - if err = chain.insertNFTokenTransaction( - nfID, adrTxID); err != nil { + if err = nftokens.InsertTransactionRelation( + chain.Conn, nfID, adrTxID); err != nil { return } } diff --git a/db/entries/entries.go b/db/entries/entries.go index 33d77fd..3d0230c 100644 --- a/db/entries/entries.go +++ b/db/entries/entries.go @@ -37,6 +37,9 @@ import ( ) // CreateTable is a SQL string that creates the "entries" table. +// +// The "entries" table has a foreign key reference to the "eblocks" table, +// which must exist first. const CreateTable = `CREATE TABLE "entries" ( "id" INTEGER PRIMARY KEY, "eb_seq" INTEGER NOT NULL, @@ -153,8 +156,10 @@ func SelectCount(conn *sqlite.Conn, validOnly bool) (int64, error) { return sqlitex.ResultInt64(stmt) } -// SelectByAddress returns the all factom.Entry where adrs and nfTkns were -// involved in the valid transaction. +// SelectByAddress returns all the factom.Entry where adrs and nfTkns were +// involved in the valid transaction, for the given pagination range. +// +// Pages start at 1. // // TODO: This should probably be moved out of the entries package and into a db // package that is more specific to FAT0 and FAT1. diff --git a/db/nftoken.go b/db/nftokens/nftokens.go similarity index 56% rename from db/nftoken.go rename to db/nftokens/nftokens.go index d4878b4..cee39d4 100644 --- a/db/nftoken.go +++ b/db/nftokens/nftokens.go @@ -20,7 +20,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package db +// Package nftokens provides functions and SQL framents for working with the +// "nf_tokens" table, which stores fat.NFToken with owner, creation id, and +// metadata. +package nftokens import ( "encoding/json" @@ -33,8 +36,35 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -func (chain *Chain) insertNFToken(nfID fat1.NFTokenID, adrID, entryID int64) (error, error) { - stmt := chain.Conn.Prep(`INSERT INTO "nf_tokens" +// CreateTable is a SQL string that creates the "nf_tokens" table. +// +// The "nf_tokens" table has foreign key references to the "entries" and +// "addresses" tables, which must exist first. +const CreateTable = `CREATE TABLE "nf_tokens" ( + "id" INTEGER PRIMARY KEY, + "metadata" BLOB, + "creation_entry_id" INTEGER NOT NULL, + "owner_id" INTEGER NOT NULL, + + FOREIGN KEY("creation_entry_id") REFERENCES "entries", + FOREIGN KEY("owner_id") REFERENCES "addresses" +); +CREATE INDEX "idx_nf_tokens_metadata" ON nf_tokens("metadata"); +CREATE INDEX "idx_nf_tokens_owner_id" ON nf_tokens("owner_id"); +CREATE VIEW "nf_tokens_addresses" AS + SELECT "nf_tokens"."id" AS "id", + "metadata", + "hash" AS "creation_hash", + "address" AS "owner" FROM + "nf_tokens", "addresses", "entries" ON + "owner_id" = "addresses"."id" AND + "creation_entry_id" = "entries"."id"; +` + +// Insert a new NFToken with "owner_id" set to the "addresses" foreign key +// adrID and the "creation_entry_id" set to the "entries" foreign key entryID. +func Insert(conn *sqlite.Conn, nfID fat1.NFTokenID, adrID, entryID int64) (error, error) { + stmt := conn.Prep(`INSERT INTO "nf_tokens" ("id", "owner_id", "creation_entry_id") VALUES (?, ?, ?);`) stmt.BindInt64(1, int64(nfID)) stmt.BindInt64(2, adrID) @@ -48,41 +78,46 @@ func (chain *Chain) insertNFToken(nfID fat1.NFTokenID, adrID, entryID int64) (er return nil, nil } -func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, adrID int64) error { - stmt := chain.Conn.Prep(`UPDATE "nf_tokens" SET "owner_id" = ? WHERE "id" = ?;`) +// SetOwner updates the "owner_id" of the given nfID to the given adrID. +// +// If the given adrID does not exist, a foreign key constraint error will be +// returned. If the nfID does not exist, this will panic. +// +// TODO: consider that the use of panic is inconsistent here. This function +// should never be called on an adrID that does not exist. Should it also panic +// on that constraint error too? Both reflect program integrity issues. +func SetOwner(conn *sqlite.Conn, nfID fat1.NFTokenID, adrID int64) error { + stmt := conn.Prep(`UPDATE "nf_tokens" SET "owner_id" = ? WHERE "id" = ?;`) stmt.BindInt64(1, adrID) stmt.BindInt64(2, int64(nfID)) _, err := stmt.Step() - if chain.Conn.Changes() == 0 { + if conn.Changes() == 0 { panic("no NFTokenID updated") } return err } -func (chain *Chain) setNFTokenMetadata(nfID fat1.NFTokenID, metadata json.RawMessage) error { - stmt := chain.Conn.Prep(`UPDATE "nf_tokens" SET "metadata" = ? WHERE "id" = ?;`) +// SetMetadata updates the "metadata" to metadata for a given nfID. +// +// If the nfID does not exist, this will panic. +func SetMetadata(conn *sqlite.Conn, nfID fat1.NFTokenID, metadata json.RawMessage) error { + stmt := conn.Prep(`UPDATE "nf_tokens" SET "metadata" = ? WHERE "id" = ?;`) stmt.BindBytes(1, metadata) stmt.BindInt64(2, int64(nfID)) _, err := stmt.Step() - if chain.Conn.Changes() == 0 { + if conn.Changes() == 0 { + // This must only be called after the nfID has been inserted. panic("no NFTokenID updated") } return err } -func (chain *Chain) insertNFTokenTransaction(nfID fat1.NFTokenID, adrTxID int64) error { - stmt := chain.Conn.Prep(`INSERT INTO "nf_token_transactions" - ("nf_tkn_id", "adr_tx_id") VALUES (?, ?);`) - stmt.BindInt64(1, int64(nfID)) - stmt.BindInt64(2, adrTxID) - - _, err := stmt.Step() - return err -} - -func SelectNFTokenOwnerID(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (int64, error) { +// SelectOwnerID returns the "owner_id" for the given nfID. +// +// If the nfID does not yet exist, (-1, nil) is returned. +func SelectOwnerID(conn *sqlite.Conn, nfID fat1.NFTokenID) (int64, error) { stmt := conn.Prep(`SELECT "owner_id" FROM "nf_tokens" WHERE "id" = ?;`) - stmt.BindInt64(1, int64(nfTkn)) + stmt.BindInt64(1, int64(nfID)) ownerID, err := sqlitex.ResultInt64(stmt) if err != nil && err.Error() == "sqlite: statement has no results" { return -1, nil @@ -93,13 +128,19 @@ func SelectNFTokenOwnerID(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (int64, error return ownerID, nil } -func SelectNFToken(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (factom.FAAddress, - factom.Bytes32, []byte, error) { +// SelectData returns the owner address, the creation entry hash, and the +// NFToken metadata for the given nfID +// +// If the nfID doesn't exist, all zero values are returned. Namely, check +// IsZero on the returned creation entry hash. +func SelectData(conn *sqlite.Conn, nfID fat1.NFTokenID) ( + factom.FAAddress, factom.Bytes32, []byte, error) { + var owner factom.FAAddress var creationHash factom.Bytes32 stmt := conn.Prep(`SELECT "owner", "metadata", "creation_hash" FROM "nf_tokens_addresses" WHERE "id" = ?;`) - stmt.BindInt64(1, int64(nfTkn)) + stmt.BindInt64(1, int64(nfID)) hasRow, err := stmt.Step() defer stmt.Reset() if err != nil { @@ -120,8 +161,12 @@ func SelectNFToken(conn *sqlite.Conn, nfTkn fat1.NFTokenID) (factom.FAAddress, return owner, creationHash, metadata, nil } -func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint) ([]fat1.NFTokenID, - []factom.FAAddress, []factom.Bytes32, [][]byte, error) { +// SelectDataAll returns the nfIDs, owner addresses, creation entry hashes, and +// the NFToken metadata for the given pagination range of NFTokens. +// +// Pages start at 1. +func SelectDataAll(conn *sqlite.Conn, order string, page, limit uint) ( + []fat1.NFTokenID, []factom.FAAddress, []factom.Bytes32, [][]byte, error) { if page == 0 { return nil, nil, nil, nil, fmt.Errorf("invalid page") } @@ -145,14 +190,12 @@ func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint) ([]fat1.N var owner factom.FAAddress if stmt.ColumnBytes(1, owner[:]) != len(owner) { - stmt.Reset() panic("invalid address length") } owners = append(owners, owner) var creationHash factom.Bytes32 if stmt.ColumnBytes(2, creationHash[:]) != len(creationHash) { - stmt.Reset() panic("invalid hash length") } creationHashes = append(creationHashes, creationHash) @@ -164,7 +207,11 @@ func SelectNFTokens(conn *sqlite.Conn, order string, page, limit uint) ([]fat1.N return tkns, owners, creationHashes, metadata, nil } -func SelectNFTokensByOwner(conn *sqlite.Conn, adr *factom.FAAddress, +// SelectByOwner returns the fat1.NFTokens owned by the given adr for the given +// pagination range. +// +// Pages start at 1. +func SelectByOwner(conn *sqlite.Conn, adr *factom.FAAddress, page, limit uint, order string) (fat1.NFTokens, error) { if page == 0 { return nil, fmt.Errorf("invalid page") diff --git a/db/nftokens/txrelation.go b/db/nftokens/txrelation.go new file mode 100644 index 0000000..a78f09f --- /dev/null +++ b/db/nftokens/txrelation.go @@ -0,0 +1,45 @@ +package nftokens + +import ( + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" +) + +// CreateTableTransactions is a SQL string that creates the +// "nf_token_transactions" table. +// +// The "nf_token_transactions" table has a foreign key reference to the +// "address_transactions" and "nf_tokens" tables, which must exist first. +const CreateTableTransactions = `CREATE TABLE "nf_token_transactions" ( + "adr_tx_id" INTEGER NOT NULL, + "nf_tkn_id" INTEGER NOT NULL, + + UNIQUE("adr_tx_id", "nf_tkn_id"), + + FOREIGN KEY("nf_tkn_id") REFERENCES "nf_tokens", + FOREIGN KEY("adr_tx_id") REFERENCES "address_transactions" +); +CREATE INDEX "idx_nf_token_transactions_adr_tx_id" ON + "nf_token_transactions"("adr_tx_id"); +CREATE INDEX "idx_nf_token_transactions_nf_tkn_id" ON + "nf_token_transactions"("nf_tkn_id"); +CREATE VIEW "nf_token_address_transactions" AS + SELECT "entry_id", "address_id", "nf_tkn_id", "to" FROM + "address_transactions" AS "adr_tx", + "nf_token_transactions" AS "tkn_tx" + ON "adr_tx"."rowid" = "tkn_tx"."adr_tx_id"; +` + +// InsertTransactionRelation inserts a row into "nf_token_transactions" +// relating the given adrTxID, a foreign row id from the "address_transactions" +// table, with the given nfID. +func InsertTransactionRelation(conn *sqlite.Conn, + nfID fat1.NFTokenID, adrTxID int64) error { + stmt := conn.Prep(`INSERT INTO "nf_token_transactions" + ("nf_tkn_id", "adr_tx_id") VALUES (?, ?);`) + stmt.BindInt64(1, int64(nfID)) + stmt.BindInt64(2, adrTxID) + + _, err := stmt.Step() + return err +} diff --git a/db/schema.go b/db/schema.go index 3aa4a7d..9d9a49b 100644 --- a/db/schema.go +++ b/db/schema.go @@ -30,63 +30,10 @@ import ( "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/db/nftokens" ) const ( - createTableAddressTransactions = `CREATE TABLE "address_transactions" ( - "entry_id" INTEGER NOT NULL, - "address_id" INTEGER NOT NULL, - "to" BOOL NOT NULL, - - PRIMARY KEY("entry_id", "address_id"), - - FOREIGN KEY("entry_id") REFERENCES "entries", - FOREIGN KEY("address_id") REFERENCES "addresses" -); -CREATE INDEX "idx_address_transactions_address_id" ON "address_transactions"("address_id"); -` - - createTableNFTokens = `CREATE TABLE "nf_tokens" ( - "id" INTEGER PRIMARY KEY, - "metadata" BLOB, - "creation_entry_id" INTEGER NOT NULL, - "owner_id" INTEGER NOT NULL, - - FOREIGN KEY("creation_entry_id") REFERENCES "entries", - FOREIGN KEY("owner_id") REFERENCES "addresses" -); -CREATE INDEX "idx_nf_tokens_metadata" ON nf_tokens("metadata"); -CREATE INDEX "idx_nf_tokens_owner_id" ON nf_tokens("owner_id"); -CREATE VIEW "nf_tokens_addresses" AS - SELECT "nf_tokens"."id" AS "id", - "metadata", - "hash" AS "creation_hash", - "address" AS "owner" FROM - "nf_tokens", "addresses", "entries" ON - "owner_id" = "addresses"."id" AND - "creation_entry_id" = "entries"."id"; -` - - createTableNFTokensTransactions = `CREATE TABLE "nf_token_transactions" ( - "adr_tx_id" INTEGER NOT NULL, - "nf_tkn_id" INTEGER NOT NULL, - - UNIQUE("adr_tx_id", "nf_tkn_id"), - - FOREIGN KEY("nf_tkn_id") REFERENCES "nf_tokens", - FOREIGN KEY("adr_tx_id") REFERENCES "address_transactions" -); -CREATE INDEX "idx_nf_token_transactions_adr_tx_id" ON - "nf_token_transactions"("adr_tx_id"); -CREATE INDEX "idx_nf_token_transactions_nf_tkn_id" ON - "nf_token_transactions"("nf_tkn_id"); -CREATE VIEW "nf_token_address_transactions" AS - SELECT "entry_id", "address_id", "nf_tkn_id", "to" FROM - "address_transactions" AS "adr_tx", - "nf_token_transactions" AS "tkn_tx" - ON "adr_tx"."rowid" = "tkn_tx"."adr_tx_id"; -` - createTableMetadata = `CREATE TABLE "metadata" ( "id" INTEGER PRIMARY KEY, "sync_height" INTEGER NOT NULL, @@ -107,9 +54,9 @@ CREATE VIEW "nf_token_address_transactions" AS chainDBSchema = eblocks.CreateTable + entries.CreateTable + addresses.CreateTable + - createTableAddressTransactions + - createTableNFTokens + - createTableNFTokensTransactions + + addresses.CreateTableTransactions + + nftokens.CreateTable + + nftokens.CreateTableTransactions + createTableMetadata ) diff --git a/srv/methods.go b/srv/methods.go index f635305..3260c83 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -30,9 +30,9 @@ import ( jrpc "github.com/AdamSLevy/jsonrpc2/v11" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/db/nftokens" "github.com/Factom-Asset-Tokens/fatd/engine" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" @@ -290,7 +290,7 @@ func getNFBalance(data json.RawMessage) interface{} { return err } - tkns, err := db.SelectNFTokensByOwner(chain.Conn, params.Address, + tkns, err := nftokens.SelectByOwner(chain.Conn, params.Address, *params.Page, params.Limit, params.Order) if err != nil { panic(err) @@ -382,7 +382,8 @@ func getNFToken(data json.RawMessage) interface{} { return err } - owner, creationHash, metadata, err := db.SelectNFToken(chain.Conn, *params.NFTokenID) + owner, creationHash, metadata, err := nftokens.SelectData( + chain.Conn, *params.NFTokenID) if err != nil { panic(err) } @@ -420,8 +421,8 @@ func getNFTokens(data json.RawMessage) interface{} { return err } - tkns, owners, creationHashes, metadata, err := db.SelectNFTokens(chain.Conn, - params.Order, *params.Page, params.Limit) + tkns, owners, creationHashes, metadata, err := nftokens.SelectDataAll( + chain.Conn, params.Order, *params.Page, params.Limit) if err != nil { panic(err) } @@ -537,7 +538,7 @@ func attemptApplyFAT1Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) } for nfID := range nfTkns { var ownerID int64 - ownerID, err = db.SelectNFTokenOwnerID(chain.Conn, nfID) + ownerID, err = nftokens.SelectOwnerID(chain.Conn, nfID) if err != nil { return } @@ -561,7 +562,8 @@ func attemptApplyFAT1Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) } for nfTkn := range nfTkns { var ownerID int64 - ownerID, err = db.SelectNFTokenOwnerID(chain.Conn, nfTkn) + ownerID, err = nftokens.SelectOwnerID( + chain.Conn, nfTkn) if err != nil { return } From 312318402e479786b2f50ffb6b27325a867858ad Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 20 Sep 2019 14:43:16 -0800 Subject: [PATCH 061/124] docs(CODE_POLICY): Add Errors section about when to panic --- CODE_POLICY.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CODE_POLICY.md b/CODE_POLICY.md index f70b20c..6783a1c 100644 --- a/CODE_POLICY.md +++ b/CODE_POLICY.md @@ -118,7 +118,28 @@ Never do this: `x := Type{}` ### Errors Errors must always be checked, even if it is extremely unlikely, or guaranteed not to occur. Most errors should cause `fatd` to cleanly exit. Only a few -specific errors should +exceptions to this rule: +- network server errors like 500 may be retried +- transaction validation errors, named `txErr` by convention + +Never report normal errors by panicking. + +#### Panic +Panics represent a program integrity error. A program integrity error is when +the program does something that is or should be impossible, or never happen. + +Examples of integrity issues: +- An out of bounds array or slice access or write +- A nil ptr dereference +- A function that must only ever be used in a certain way, with valid inputs. + e.g. regexp.MustCompile + +The idea of an integrity error, is that when the program is written correctly, +this should never occur. So if this occurs, the program is misusing something +critical. + +If you panic on an error, you should explain in comments why the error +represents an integrity issue, if it is not exceedingly obvious. ### Types From 08a5ac258c7f937e5b6c6069e582569dc6d45bfd Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 20 Sep 2019 16:37:17 -0800 Subject: [PATCH 062/124] refactor(db): Add db/metadata for metadata table --- db/apply.go | 7 +- db/chain.go | 62 +++++++++++++- db/metadata.go | 184 ---------------------------------------- db/metadata/metadata.go | 182 +++++++++++++++++++++++++++++++++++++++ db/schema.go | 18 +--- 5 files changed, 249 insertions(+), 204 deletions(-) delete mode 100644 db/metadata.go create mode 100644 db/metadata/metadata.go diff --git a/db/apply.go b/db/apply.go index 0396f27..9eb06ab 100644 --- a/db/apply.go +++ b/db/apply.go @@ -30,6 +30,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/db/metadata" "github.com/Factom-Asset-Tokens/fatd/db/nftokens" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" @@ -100,7 +101,7 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error if issueErr = issuance.Validate(chain.ID1); issueErr != nil { return } - if err = chain.setInitEntryID(ei); err != nil { + if err = metadata.SetInitEntryID(chain.Conn, ei); err != nil { return } chain.Issuance = issuance @@ -197,7 +198,7 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, txErr = fmt.Errorf("coinbase exceeds max supply") return } - if err = chain.numIssuedAdd(addIssued); err != nil { + if err = chain.addNumIssued(addIssued); err != nil { return } if _, err = addresses.InsertTransactionRelation( @@ -249,7 +250,7 @@ func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, txErr = fmt.Errorf("coinbase exceeds max supply") return } - if err = chain.numIssuedAdd(addIssued); err != nil { + if err = chain.addNumIssued(addIssued); err != nil { return } var adrTxID int64 diff --git a/db/chain.go b/db/chain.go index dee1263..78b766a 100644 --- a/db/chain.go +++ b/db/chain.go @@ -35,6 +35,9 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/db/addresses" + "github.com/Factom-Asset-Tokens/fatd/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/db/metadata" "github.com/Factom-Asset-Tokens/fatd/fat" _log "github.com/Factom-Asset-Tokens/fatd/log" ) @@ -119,7 +122,8 @@ func OpenNew(dbPath string, chain.SyncDBKeyMR = dbKeyMR chain.NetworkID = networkID - if err = chain.insertMetadata(); err != nil { + if err = metadata.Insert(chain.Conn, chain.SyncHeight, chain.SyncDBKeyMR, + chain.NetworkID, chain.Identity); err != nil { return } @@ -262,3 +266,59 @@ func (chain *Chain) LatestEntryTimestamp() time.Time { lastID := len(entries) - 1 return entries[lastID].Timestamp } + +func (chain *Chain) SetSync(height uint32, dbKeyMR *factom.Bytes32) error { + if height <= chain.SyncHeight { + return nil + } + if err := metadata.SetSync(chain.Conn, height, dbKeyMR); err != nil { + return err + } + chain.SyncHeight = height + chain.SyncDBKeyMR = dbKeyMR + return nil +} + +func (chain *Chain) addNumIssued(add uint64) error { + if err := metadata.AddNumIssued(chain.Conn, add); err != nil { + return err + } + chain.NumIssued += add + return nil +} + +func (chain *Chain) loadMetadata() error { + defer chain.setApplyFunc() + // Load NameIDs + first, err := entries.SelectByID(chain.Conn, 1) + if err != nil { + return err + } + if !first.IsPopulated() { + return fmt.Errorf("no first entry") + } + + nameIDs := first.ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + return fmt.Errorf("invalid token chain Name IDs") + } + chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) + + // Load Chain Head + eb, dbKeyMR, err := eblocks.SelectLatest(chain.Conn) + if err != nil { + return err + } + if !eb.IsPopulated() { + // A database must always have at least one EBlock. + return fmt.Errorf("no eblock in database") + } + chain.Head = eb + chain.DBKeyMR = &dbKeyMR + chain.ID = eb.ChainID + + chain.SyncHeight, chain.NumIssued, chain.SyncDBKeyMR, + chain.NetworkID, chain.Identity, + chain.Issuance, err = metadata.Select(chain.Conn) + return err +} diff --git a/db/metadata.go b/db/metadata.go deleted file mode 100644 index b7d6d39..0000000 --- a/db/metadata.go +++ /dev/null @@ -1,184 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package db - -import ( - "fmt" - - "crawshaw.io/sqlite" - "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/eblocks" - "github.com/Factom-Asset-Tokens/fatd/db/entries" - "github.com/Factom-Asset-Tokens/fatd/fat" -) - -func (chain *Chain) insertMetadata() error { - stmt := chain.Conn.Prep(`INSERT INTO "metadata" - ("id", "sync_height", "sync_db_key_mr", - "network_id", "id_key_entry", "id_key_height") - VALUES (0, ?, ?, ?, ?, ?);`) - stmt.BindInt64(1, int64(chain.SyncHeight)) - stmt.BindBytes(2, chain.SyncDBKeyMR[:]) - stmt.BindBytes(3, chain.NetworkID[:]) - if chain.Identity.IsPopulated() { - data, err := chain.Identity.MarshalBinary() - if err != nil { - return err - } - stmt.BindBytes(4, data) - stmt.BindInt64(5, int64(chain.Identity.Height)) - } else { - stmt.BindNull(4) - stmt.BindNull(5) - } - _, err := stmt.Step() - return err -} - -func (chain *Chain) SetSync(height uint32, dbKeyMR *factom.Bytes32) error { - if height <= chain.SyncHeight { - return nil - } - stmt := chain.Conn.Prep(`UPDATE "metadata" SET - ("sync_height", "sync_db_key_mr") = (?, ?) WHERE "id" = 0;`) - stmt.BindInt64(1, int64(height)) - stmt.BindBytes(2, dbKeyMR[:]) - _, err := stmt.Step() - if chain.Conn.Changes() == 0 { - panic("nothing updated") - } - chain.SyncHeight = height - chain.SyncDBKeyMR = dbKeyMR - return err -} - -func (chain *Chain) setInitEntryID(id int64) error { - stmt := chain.Conn.Prep(`UPDATE "metadata" SET - ("init_entry_id", "num_issued") = (?, 0) WHERE "id" = 0;`) - stmt.BindInt64(1, id) - _, err := stmt.Step() - if chain.Conn.Changes() == 0 { - panic("nothing updated") - } - return err -} - -func (chain *Chain) numIssuedAdd(add uint64) error { - stmt := chain.Conn.Prep(`UPDATE "metadata" SET - "num_issued" = "num_issued" + ? WHERE "id" = 0;`) - stmt.BindInt64(1, int64(add)) - _, err := stmt.Step() - if chain.Conn.Changes() == 0 { - panic("nothing updated") - } - chain.NumIssued += add - return err -} - -func (chain *Chain) loadMetadata() error { - defer chain.setApplyFunc() - // Load NameIDs - first, err := entries.SelectByID(chain.Conn, 1) - if err != nil { - return err - } - if !first.IsPopulated() { - return fmt.Errorf("no first entry") - } - - nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { - return fmt.Errorf("invalid token chain Name IDs") - } - chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) - - // Load Chain Head - eb, dbKeyMR, err := eblocks.SelectLatest(chain.Conn) - if err != nil { - return err - } - if !eb.IsPopulated() { - // A database must always have at least one EBlock. - return fmt.Errorf("no eblock in database") - } - chain.Head = eb - chain.DBKeyMR = &dbKeyMR - chain.ID = eb.ChainID - - stmt := chain.Conn.Prep(`SELECT "sync_height", "sync_db_key_mr", "network_id", - "id_key_entry", "id_key_height", "init_entry_id", "num_issued" - FROM "metadata";`) - hasRow, err := stmt.Step() - defer stmt.Reset() - if err != nil { - return err - } - if !hasRow { - return fmt.Errorf("no saved metadata") - } - - chain.SyncHeight = uint32(stmt.ColumnInt64(0)) - - chain.SyncDBKeyMR = new(factom.Bytes32) - if stmt.ColumnBytes(1, chain.SyncDBKeyMR[:]) != len(chain.SyncDBKeyMR) { - return fmt.Errorf("invalid sync_db_key_mr length") - } - - if stmt.ColumnBytes(2, chain.NetworkID[:]) != len(chain.NetworkID) { - return fmt.Errorf("invalid network_id length") - } - - // Load chain.Identity... - if stmt.ColumnType(3) == sqlite.SQLITE_NULL { - // No Identity, therefore no Issuance. - return nil - } - idKeyEntryData := make(factom.Bytes, stmt.ColumnLen(3)) - stmt.ColumnBytes(3, idKeyEntryData) - if err := chain.Identity.UnmarshalBinary(idKeyEntryData); err != nil { - return fmt.Errorf("chain.Identity.UnmarshalBinary(): %v", err) - } - chain.Identity.Height = uint32(stmt.ColumnInt64(4)) - if *chain.Identity.ChainID != *chain.IssuerChainID { - return fmt.Errorf("invalid chain.Identity.ChainID") - } - chain.Identity.ChainID = chain.IssuerChainID // free mem from duplicates - - // Load chain.Issuance... - if stmt.ColumnType(5) == sqlite.SQLITE_NULL { - // No issuance entry so far... - return nil - } - initEntryID := stmt.ColumnInt64(5) - chain.Issuance.Entry.Entry, err = entries.SelectByID(chain.Conn, initEntryID) - if err != nil { - return err - } - if err := chain.Issuance.Validate(chain.ID1); err != nil { - return err - } - - chain.NumIssued = uint64(stmt.ColumnInt64(6)) - - return nil -} diff --git a/db/metadata/metadata.go b/db/metadata/metadata.go new file mode 100644 index 0000000..ca3835b --- /dev/null +++ b/db/metadata/metadata.go @@ -0,0 +1,182 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Package metadata provides functions and SQL framents for working with the +// "metadata" table, which stores the sync height, sync DBKeyMR, +// factom.NetworkID, and factom.Identity. +package metadata + +import ( + "fmt" + + "crawshaw.io/sqlite" + "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/fat" +) + +// CreateTable is a SQL string that creates the "metadata" table. +// +// The "metadata" table has a foreign key reference to the "entries" table, +// which must exist first. +const CreateTable = `CREATE TABLE "metadata" ( + "id" INTEGER PRIMARY KEY, + "sync_height" INTEGER NOT NULL, + "sync_db_key_mr" BLOB NOT NULL, + "network_id" BLOB NOT NULL, + "id_key_entry" BLOB, + "id_key_height" INTEGER, + + "init_entry_id" INTEGER, + "num_issued" INTEGER, + + FOREIGN KEY("init_entry_id") REFERENCES "entries" +); +` + +// Insert the syncHeight, syncDBKeyMR, networkID, and if populated, the +// identity into the first row of the "metadata" table. This may only ever be +// called once for a given database. +func Insert(conn *sqlite.Conn, syncHeight uint32, syncDBKeyMR *factom.Bytes32, + networkID factom.NetworkID, identity factom.Identity) error { + stmt := conn.Prep(`INSERT INTO "metadata" + ("id", "sync_height", "sync_db_key_mr", + "network_id", "id_key_entry", "id_key_height") + VALUES (0, ?, ?, ?, ?, ?);`) + stmt.BindInt64(1, int64(syncHeight)) + stmt.BindBytes(2, syncDBKeyMR[:]) + stmt.BindBytes(3, networkID[:]) + if identity.IsPopulated() { + data, err := identity.MarshalBinary() + if err != nil { + return err + } + stmt.BindBytes(4, data) + stmt.BindInt64(5, int64(identity.Height)) + } else { + stmt.BindNull(4) + stmt.BindNull(5) + } + _, err := stmt.Step() + return err +} + +// SetSync updates the "sync_height" and "sync_db_key_mr". +func SetSync(conn *sqlite.Conn, height uint32, dbKeyMR *factom.Bytes32) error { + stmt := conn.Prep(`UPDATE "metadata" SET + ("sync_height", "sync_db_key_mr") = (?, ?);`) + stmt.BindInt64(1, int64(height)) + stmt.BindBytes(2, dbKeyMR[:]) + _, err := stmt.Step() + if err != nil && conn.Changes() != 1 { + panic(fmt.Errorf("expected exactly 1 change but got %v", + conn.Changes())) + } + return err +} + +// SetInitEntryID updates the "init_entry_id" +func SetInitEntryID(conn *sqlite.Conn, id int64) error { + stmt := conn.Prep(`UPDATE "metadata" SET + ("init_entry_id", "num_issued") = (?, 0);`) + stmt.BindInt64(1, id) + _, err := stmt.Step() + if err != nil && conn.Changes() != 1 { + panic(fmt.Errorf("expected exactly 1 change but got %v", + conn.Changes())) + } + return err +} + +func AddNumIssued(conn *sqlite.Conn, add uint64) error { + stmt := conn.Prep(`UPDATE "metadata" SET + "num_issued" = "num_issued" + ?;`) + stmt.BindInt64(1, int64(add)) + _, err := stmt.Step() + if err != nil && conn.Changes() != 1 { + panic(fmt.Errorf("expected exactly 1 change but got %v", + conn.Changes())) + } + return err +} + +func Select(conn *sqlite.Conn) (syncHeight uint32, numIssued uint64, + syncDBKeyMR *factom.Bytes32, + networkID factom.NetworkID, + identity factom.Identity, + issuance fat.Issuance, + err error) { + stmt := conn.Prep(`SELECT "sync_height", "sync_db_key_mr", "network_id", + "id_key_entry", "id_key_height", "init_entry_id", "num_issued" + FROM "metadata";`) + hasRow, err := stmt.Step() + defer stmt.Reset() + if err != nil { + return + } + if !hasRow { + err = fmt.Errorf("no saved metadata") + return + } + + syncHeight = uint32(stmt.ColumnInt64(0)) + + syncDBKeyMR = new(factom.Bytes32) + if stmt.ColumnBytes(1, syncDBKeyMR[:]) != len(syncDBKeyMR) { + panic("invalid sync_db_key_mr length") + } + + if stmt.ColumnBytes(2, networkID[:]) != len(networkID) { + panic("invalid network_id length") + } + + // Load chain.Identity... + if stmt.ColumnType(3) == sqlite.SQLITE_NULL { + // No Identity, therefore no Issuance. + return + } + idKeyEntryData := make(factom.Bytes, stmt.ColumnLen(3)) + stmt.ColumnBytes(3, idKeyEntryData) + if err = identity.UnmarshalBinary(idKeyEntryData); err != nil { + err = fmt.Errorf("identity.UnmarshalBinary(): %v", err) + return + } + identity.Height = uint32(stmt.ColumnInt64(4)) + + // Load chain.Issuance... + if stmt.ColumnType(5) == sqlite.SQLITE_NULL { + // No issuance entry so far... + return + } + initEntryID := stmt.ColumnInt64(5) + issuance.Entry.Entry, err = entries.SelectByID(conn, initEntryID) + if err != nil { + return + } + if err = issuance.Validate(identity.ID1); err != nil { + return + } + + numIssued = uint64(stmt.ColumnInt64(6)) + + return +} diff --git a/db/schema.go b/db/schema.go index 9d9a49b..8ad8874 100644 --- a/db/schema.go +++ b/db/schema.go @@ -30,25 +30,11 @@ import ( "github.com/Factom-Asset-Tokens/fatd/db/addresses" "github.com/Factom-Asset-Tokens/fatd/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/db/metadata" "github.com/Factom-Asset-Tokens/fatd/db/nftokens" ) const ( - createTableMetadata = `CREATE TABLE "metadata" ( - "id" INTEGER PRIMARY KEY, - "sync_height" INTEGER NOT NULL, - "sync_db_key_mr" BLOB NOT NULL, - "network_id" BLOB NOT NULL, - "id_key_entry" BLOB, - "id_key_height" INTEGER, - - "init_entry_id" INTEGER, - "num_issued" INTEGER, - - FOREIGN KEY("init_entry_id") REFERENCES "entries" -); -` - // For the sake of simplicity, all chain DBs use the exact same schema, // regardless of whether they actually make use of the NFTokens tables. chainDBSchema = eblocks.CreateTable + @@ -57,7 +43,7 @@ const ( addresses.CreateTableTransactions + nftokens.CreateTable + nftokens.CreateTableTransactions + - createTableMetadata + metadata.CreateTable ) // validateOrApplySchema compares schema with the database connected to by From 92700e68f43878ed7307c8b2efac9b43c5a2411c Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 25 Sep 2019 10:54:27 -0800 Subject: [PATCH 063/124] build(go.mod): Update github.com/AdamSLevy/sqlite --- go.mod | 4 ++-- go.sum | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index c0e8dc8..e4cd576 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 - github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e + github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect @@ -23,4 +23,4 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455 +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 diff --git a/go.sum b/go.sum index c566295..a57f4a5 100644 --- a/go.sum +++ b/go.sum @@ -7,10 +7,10 @@ github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 h1:McSW/pP7K0/Ucjig6AJwW7Khph/XOMYhSB8 github.com/AdamSLevy/jsonrpc2/v11 v11.3.2/go.mod h1:7fNjH6BXM0KVswWqj+K/mnOS8wiSke0sE8X46hS+nsc= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455 h1:f3m3JW4YkEDN62N036HWMFbLgT+wVgqd5UUunixT2PQ= -github.com/AdamSLevy/sqlite v0.1.3-0.20190909223149-1ad299dae455/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e h1:wRP8CEe6U4Q4tSD0GWUUoQ0uICteUsjD4nNkvFms2k8= -github.com/AdamSLevy/sqlitechangeset v0.0.0-20190910025316-7fba69e6303e/go.mod h1:AL34KMJ3RXL3U2gX6MWurmneH0u/ergvXeVTsUBufcc= +github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 h1:eNbDxd8OsXjADVKdoDZEQwLqA08rzCUxvgnOiZAf6hs= +github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d h1:yPm4An70OhM4k4WUq7M9sWaVlFas2+hJB+I3Fsgw38A= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= @@ -177,8 +177,6 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b h1:3S2h5FadpNr0zUUCVZjlKIEYF+KaX/OBplTGo89CYHI= -golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5 h1:SW/0nsKCUaozCUtZTakri5laocGx/5bkDSSLrFUsa5s= golang.org/x/sys v0.0.0-20190911201528-7ad0cfa0b7b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= From 1a1cc9fd29e8e901afd332789a623d5c7b58270e Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 25 Sep 2019 11:02:27 -0800 Subject: [PATCH 064/124] fix(db): Update generate to use new OpenNew --- db/gentestdb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/gentestdb.go b/db/gentestdb.go index b069909..665be56 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -38,7 +38,7 @@ import ( func init() { log.SetFlags(log.Lshortfile) - fflag.DBPath = "./test-fatd.db" + fflag.DBPath = "./test-fatd.db/" fflag.LogDebug = true } @@ -90,7 +90,7 @@ func main() { } // We don't need the actual dbKeyMR - chain, err := db.OpenNew(dblock.KeyMR, first, MainnetID(), identity) + chain, err := db.OpenNew(fflag.DBPath, dblock.KeyMR, first, MainnetID(), identity) if err != nil { log.Println(err) return From 23bd237c95b3ceb6638d9cdb6f7ee58d9e0aa9a5 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 25 Sep 2019 15:54:32 -0800 Subject: [PATCH 065/124] fix(db/sqlbuilder): Correct SQLBuilder.OrderBy() syntax --- db/sqlbuilder/sqlbuilder.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/db/sqlbuilder/sqlbuilder.go b/db/sqlbuilder/sqlbuilder.go index 60b7f36..f4782cf 100644 --- a/db/sqlbuilder/sqlbuilder.go +++ b/db/sqlbuilder/sqlbuilder.go @@ -109,8 +109,6 @@ func (s *SQLBuilder) Paginate(page, limit uint) { // OrderBy append fmt.Sprintf(` ORDER BY %q %s`, col, order). No binds are // added. func (s *SQLBuilder) OrderBy(col, ascDesc string) { - s.WriteString(col) - s.WriteString(`"`) ascDesc = strings.ToUpper(ascDesc) switch ascDesc { case "ASC", "DESC": From 81408234a2b57cff3fe06eaaf137b09b0515a708 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 27 Sep 2019 12:01:30 -0800 Subject: [PATCH 066/124] docs(CODE_POLICY): Add section on line length and comments --- CODE_POLICY.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CODE_POLICY.md b/CODE_POLICY.md index 6783a1c..22bac9f 100644 --- a/CODE_POLICY.md +++ b/CODE_POLICY.md @@ -85,6 +85,49 @@ or `develop`) to your feature. ## Golang +### Formatting + +#### Gofmt + +Use the official gofmt tool. It is highly recommended to run gofmt on every +save, as this will always catch any syntax errors and ensure consistent +formatting. Virtually every IDE and code editor has a plugin that runs gofmt on +save. + +Do not commit code that has not been run through the formatter. + +#### Line length + +Please limit line length to 79 characters max to avoid the need for horizontal +scrolling. This also applies to comments. Indented comments must still limit +the total line length. Most everything in golang can be broken up across +multiple lines to properly limit line length. + +The line length max for many modern editors are set to 80 characters max now. +But the default in vim is 79 for historical reasons, and so this will be +perpetuated in this project as well. + +The one exception to the max line length are string literals. In order to allow +string literals to be easily searched for in the codebase, these should not be +broken up across lines. However always insert a newline before the string +literal declaration if this allows the line length rule to be respected. + +#### Comments + +Comments must respect line length rules (see above). Comments should be used to +document exported APIs, and explain non-obvious or complex code. + +Comments must be complete english sentences. They should be concise and +precise. In general, they should explain the intent or high level behavior of +code, and only explain non-obvious implementation details or design decisions. + +Comments must be kept up to date with the code they describe. When you change +code, you must review the comments for discrepancies. + +When comments describe behavior you must not change the behavior, as this +represents an API level change. Such changes must be discussed and well +understood so that any code depending on the behavior can be updated. + ### Imports and modules Importing external code should be done with care. Imports should be evaluated From dd3c803bd2f2a61d451ba45100d446bbd2d9020f Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 1 Oct 2019 16:06:37 -0800 Subject: [PATCH 067/124] fix(db,engine): Use sqlite.Snapshots for pending transactions Previously two copies of databases were used for pending transactions. Using snapshots greatly simplifies this. --- cli/cmd/issue.go | 10 +-- cli/cmd/root.go | 6 +- db/chain.go | 13 +--- db/validate.go | 51 ++++-------- engine/chain.go | 107 +++---------------------- engine/chainmap.go | 11 ++- engine/engine.go | 166 +++++++++++++++++++++++---------------- engine/process.go | 190 ++++++++++++++++++++++++++++++++------------- fat/chainid.go | 15 ++-- flag/flag.go | 11 +-- go.mod | 3 +- go.sum | 6 +- srv/client.go | 2 +- srv/methods.go | 17 ++-- srv/params.go | 7 +- 15 files changed, 313 insertions(+), 302 deletions(-) diff --git a/cli/cmd/issue.go b/cli/cmd/issue.go index 02e86e1..24a637c 100644 --- a/cli/cmd/issue.go +++ b/cli/cmd/issue.go @@ -367,14 +367,12 @@ func printCurl(entry factom.Entry, es factom.EsAddress) error { } vrbLog.Println("Curl commands:") - commitHex, _ := factom.Bytes(commit).MarshalJSON() - fmt.Printf(`curl -X POST --data-binary '{"jsonrpc": "2.0", "id": 0, "method": "%v", "params":{"message":%v}}' -H 'content-type:text/plain;' %v/v2`, - commitMethod, string(commitHex), FactomClient.FactomdServer) + fmt.Printf(`curl -X POST --data-binary '{"jsonrpc":"2.0","id":0,"method":%q,"params":{"message":%q}}' -H 'content-type:text/plain;' %v`, + commitMethod, factom.Bytes(commit), FactomClient.FactomdServer) fmt.Println() - revealHex, _ := factom.Bytes(reveal).MarshalJSON() - fmt.Printf(`curl -X POST --data-binary '{"jsonrpc": "2.0", "id": 0, "method": "%v", "params":{"entry":%v}}' -H 'content-type:text/plain;' %v/v2`, - revealMethod, string(revealHex), FactomClient.FactomdServer) + fmt.Printf(`curl -X POST --data-binary '{"jsonrpc":"2.0","id": 0,"method":%q,"params":{"entry":%q}}' -H 'content-type:text/plain;' %v`, + revealMethod, factom.Bytes(reveal), FactomClient.FactomdServer) fmt.Println() return nil } diff --git a/cli/cmd/root.go b/cli/cmd/root.go index de8394b..6c663a1 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -30,7 +30,7 @@ import ( "strings" "time" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/srv" @@ -65,7 +65,7 @@ func initClients() { // Use --debugwalletd explicitly to debug wallet API calls. } - for _, client := range []*jrpc.Client{ + for _, client := range []*jsonrpc2.Client{ &FATClient.Client, &FactomClient.Factomd, &FactomClient.Walletd, @@ -435,7 +435,7 @@ func validateChainIDFlags(cmd *cobra.Command, _ []string) error { } func initChainID() { NameIDs = fat.NameIDs(paramsToken.TokenID, paramsToken.IssuerChainID) - *paramsToken.ChainID = factom.ChainID(NameIDs) + *paramsToken.ChainID = factom.ComputeChainID(NameIDs) vrbLog.Println("Token Chain ID:", paramsToken.ChainID) } diff --git a/db/chain.go b/db/chain.go index 78b766a..e425a38 100644 --- a/db/chain.go +++ b/db/chain.go @@ -115,7 +115,8 @@ func OpenNew(dbPath string, chain.Log = _log.New("chain", strings.TrimRight(fname, dbFileExtension)) chain.DBFile = fname chain.ID = eb.ChainID - chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) + chain.IssuerChainID = new(factom.Bytes32) + chain.TokenID, *chain.IssuerChainID = fat.TokenIssuer(nameIDs) chain.DBKeyMR = dbKeyMR chain.Identity = identity chain.SyncHeight = eb.Height @@ -254,13 +255,6 @@ func (chain *Chain) Close() { } } -// Get() returns a threadsafe connection to the database, and a function to -// release the connection back to the pool. -func (chain *Chain) Get() (*sqlite.Conn, func()) { - conn := chain.Pool.Get(nil) - return conn, func() { chain.Pool.Put(conn) } -} - func (chain *Chain) LatestEntryTimestamp() time.Time { entries := chain.Head.Entries lastID := len(entries) - 1 @@ -302,7 +296,8 @@ func (chain *Chain) loadMetadata() error { if !fat.ValidTokenNameIDs(nameIDs) { return fmt.Errorf("invalid token chain Name IDs") } - chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) + chain.IssuerChainID = new(factom.Bytes32) + chain.TokenID, *chain.IssuerChainID = fat.TokenIssuer(nameIDs) // Load Chain Head eb, dbKeyMR, err := eblocks.SelectLatest(chain.Conn) diff --git a/db/validate.go b/db/validate.go index 5453fa4..813cb64 100644 --- a/db/validate.go +++ b/db/validate.go @@ -40,9 +40,10 @@ func init() { sqlitechangeset.AlwaysUseBlob = true } -// ValidateChain validates all Entry Hashes and EBlock KeyMRs, as well as the -// continuity of all stored EBlocks and Entries. It does not validate the -// validity of the saved DBlock KeyMRs. +// Validate all Entry Hashes and EBlock KeyMRs, as well as the continuity of +// all stored EBlocks and Entries. +// +// This does not validate the validity of the saved DBlock KeyMRs. func (chain Chain) Validate() (err error) { chain.Log.Debug("Validating database...") // Validate ChainID... @@ -56,7 +57,7 @@ func (chain Chain) Validate() (err error) { if !first.IsPopulated() { return fmt.Errorf("no entries") } - if *chain.ID != factom.ChainID(first.ExtIDs) { + if *chain.ID != factom.ComputeChainID(first.ExtIDs) { return fmt.Errorf("invalid NameIDs") } @@ -98,7 +99,7 @@ func (chain Chain) Validate() (err error) { var eID int = 1 // Entry ID var sequence uint32 // EBlock Sequence - var prevKeyMR, prevFullHash factom.Bytes32 + var prevKeyMR, prevFullHash *factom.Bytes32 for { eb, err := eblocks.Select(eBlockStmt) if err != nil { @@ -109,56 +110,34 @@ func (chain Chain) Validate() (err error) { break } - if *eb.ChainID != *chain.ID { - return fmt.Errorf("invalid EBlock{%v, %v}: invalid ChainID", - eb.Sequence, eb.KeyMR) - } - if sequence != eb.Sequence { return fmt.Errorf("invalid EBlock{%v, %v}: invalid Sequence", eb.Sequence, eb.KeyMR) } sequence++ - if *eb.PrevKeyMR != prevKeyMR { + if (prevKeyMR != nil && *eb.PrevKeyMR != *prevKeyMR) || + (prevKeyMR == nil && !eb.PrevKeyMR.IsZero()) { return fmt.Errorf("invalid EBlock{%v, %v}: broken PrevKeyMR link", eb.Sequence, eb.KeyMR) } + prevKeyMR = eb.KeyMR - if *eb.PrevFullHash != prevFullHash { + if (prevFullHash != nil && *eb.PrevFullHash != *prevFullHash) || + (prevFullHash == nil && !eb.PrevFullHash.IsZero()) { return fmt.Errorf("invalid EBlock{%v, %v}: broken FullHash link", eb.Sequence, eb.KeyMR) } - - keyMR, err := eb.ComputeKeyMR() - if err != nil { - return err - } - if keyMR != *eb.KeyMR { - return fmt.Errorf("invalid EBlock%+v: invalid KeyMR: %v", - eb, keyMR) - } - - prevFullHash, err = eb.ComputeFullHash() - if err != nil { - return err - } - prevKeyMR = keyMR + prevFullHash = eb.FullHash for i, ebe := range eb.Entries { e, err := entries.Select(entryStmt) - - if *e.Hash != *ebe.Hash { - return fmt.Errorf("invalid Entry{%v}: broken EBlock link", - e.Hash) - } - - hash, err := e.ComputeHash() if err != nil { return err } - if hash != *e.Hash { - return fmt.Errorf("invalid Entry{%v}: invalid Hash", + + if *e.Hash != *ebe.Hash { + return fmt.Errorf("invalid Entry{%v}: broken EBlock link", e.Hash) } diff --git a/engine/chain.go b/engine/chain.go index 8eb300a..e83ae0b 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -23,15 +23,14 @@ package engine import ( - "bytes" "fmt" "crawshaw.io/sqlite" jrpc "github.com/AdamSLevy/jsonrpc2/v11" - "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" ) @@ -44,72 +43,10 @@ type Chain struct { } type Pending struct { - PendSess *sqlite.Session - MainSess *sqlite.Session - Chain db.Chain - Entries map[factom.Bytes32]factom.Entry -} - -func (p *Pending) Close() { - p.DeleteSessions() - p.Chain.Close() -} - -func (p *Pending) DeleteSessions() { - if p.PendSess != nil { - p.PendSess.Delete() - p.PendSess = nil - } - if p.MainSess != nil { - p.MainSess.Delete() - p.MainSess = nil - } -} - -func (p *Pending) Sync(chain db.Chain) error { - // Reset p.Chain to chain, but preserve the existing Conn and Pool. - conn, pool := p.Chain.Conn, p.Chain.Pool - p.Chain = chain - p.Chain.Conn, p.Chain.Pool = conn, pool - - p.Entries = nil - - // Ensure the sessions are deleted and freed. - defer p.DeleteSessions() - - if p.PendSess != nil { - // Revert all of the pending transactions by applying the inverse of - // the changeset tracked by session. - changeset := &bytes.Buffer{} - if err := p.PendSess.Changeset(changeset); err != nil { - return err - } - inverse := bytes.NewBuffer(make([]byte, 0, changeset.Cap())) - if err := sqlite.ChangesetInvert(inverse, changeset); err != nil { - return err - } - conflictFn := func(cType sqlite.ConflictType, - _ sqlite.ChangesetIter) sqlite.ConflictAction { - chain.Log.Errorf("ChangesetApply Conflict: %v", cType) - return sqlite.SQLITE_CHANGESET_ABORT - } - if err := p.Chain.Conn.ChangesetApply(inverse, nil, conflictFn); err != nil { - return err - } - } - - if p.MainSess != nil { - // Apply all of the official transactions. - changeset := &bytes.Buffer{} - if err := p.MainSess.Changeset(changeset); err != nil { - return err - } - if err := p.Chain.Conn.ChangesetApply(changeset, nil, nil); err != nil { - return err - } - } - - return nil + Session *sqlite.Session + OfficialSnapshot *sqlite.Snapshot + OfficialChain db.Chain + Entries map[factom.Bytes32]factom.Entry } func (chain Chain) String() string { @@ -140,7 +77,8 @@ func OpenNew(c *factom.Client, } var identity factom.Identity - _, identity.ChainID = fat.TokenIssuer(nameIDs) + identity.ChainID = new(factom.Bytes32) + _, *identity.ChainID = fat.TokenIssuer(nameIDs) if err = identity.Get(c); err != nil { // A jrpc.Error indicates that the identity chain // doesn't yet exist, which we tolerate. @@ -162,7 +100,6 @@ func OpenNew(c *factom.Client, } else { chain.ChainStatus = ChainStatusTracked } - chain.Pending.Chain = chain.Chain return } @@ -175,11 +112,11 @@ func (chain *Chain) OpenNewByChainID(c *factom.Client, chainID *factom.Bytes32) first := eblocks[len(eblocks)-1] // Get DBlock Timestamp and KeyMR var dblock factom.DBlock - dblock.Header.Height = first.Height + dblock.Height = first.Height if err := dblock.Get(c); err != nil { return fmt.Errorf("factom.DBlock{}.Get(): %v", err) } - first.SetTimestamp(dblock.Header.Timestamp) + first.SetTimestamp(dblock.Timestamp) *chain, err = OpenNew(c, dblock.KeyMR, first) if err != nil { @@ -207,11 +144,11 @@ func (chain *Chain) SyncEBlocks(c *factom.Client, ebs []factom.EBlock) error { // Get DBlock Timestamp and KeyMR var dblock factom.DBlock - dblock.Header.Height = eb.Height + dblock.Height = eb.Height if err := dblock.Get(c); err != nil { return fmt.Errorf("factom.DBlock{}.Get(): %v", err) } - eb.SetTimestamp(dblock.Header.Timestamp) + eb.SetTimestamp(dblock.Timestamp) if err := chain.Apply(c, dblock.KeyMR, eb); err != nil { return err @@ -243,25 +180,3 @@ func (chain *Chain) Apply(c *factom.Client, } return nil } - -func (p *Pending) Open(chain db.Chain) (err error) { - dbURI := fmt.Sprintf("file:%v?mode=memory&cache=shared", chain.ID) - c, err := chain.Conn.BackupToDB("", dbURI) - if err != nil { - return - } - // Close connection to in memory database only after we have the other - // connections open. - defer c.Close() - - // Open the pending database connection and pool - conn, pool, err := db.OpenConnPool(dbURI) - if err != nil { - return - } - p.Chain = chain - log := p.Chain.Log.Entry - p.Chain.Log.Entry = log.WithField("db", "pending") - p.Chain.Conn, p.Chain.Pool = conn, pool - return -} diff --git a/engine/chainmap.go b/engine/chainmap.go index 8348731..5bb869a 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -27,8 +27,8 @@ import ( "math" "sync" - "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/flag" ) @@ -108,8 +108,13 @@ func (cm ChainMap) Close() { cm.Lock() for _, chain := range cm.m { if chain.IsTracked() { - if chain.Pending.Chain.Conn != nil { - chain.Pending.Close() + if chain.Pending.Session != nil { + chain.Pending.Session.Delete() + chain.Pending.Session = nil + } + if chain.Pending.OfficialSnapshot != nil { + chain.Pending.OfficialSnapshot.Free() + chain.Pending.OfficialSnapshot = nil } chain.Close() } diff --git a/engine/engine.go b/engine/engine.go index 1bb9995..b538bfe 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -25,6 +25,7 @@ package engine import ( "fmt" "os" + "runtime" "sync" "time" @@ -55,9 +56,11 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { // Try to create the main and pending database directories, in case // they don't already exist. - if err := createDir(flag.DBPath); err != nil { - log.Error(err) - return + if err := os.Mkdir(flag.DBPath, 0755); err != nil { + if !os.IsExist(err) { + log.Error(fmt.Errorf("os.Mkdir(%#v): %v", flag.DBPath, err)) + return nil + } } // Try to create a lockfile @@ -72,6 +75,7 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { log.Errorf("Database in use by other process. %v", err) return } + // Always clean up the lockfile if Start fails. defer func() { if done == nil { if err := lockFile.Unlock(); err != nil { @@ -86,14 +90,14 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { return } var dblock factom.DBlock - dblock.Header.Height = factomHeight + dblock.Height = factomHeight if err := dblock.Get(c); err != nil { log.Errorf("dblock.Get(): %v", err) return } - if dblock.Header.NetworkID != flag.NetworkID { + if dblock.NetworkID != flag.NetworkID { log.Errorf("invalid Factom Blockchain NetworkID: %v, expected: %v", - dblock.Header.NetworkID, flag.NetworkID) + dblock.NetworkID, flag.NetworkID) return } @@ -107,6 +111,7 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { log.Error(err) return } + // Always close all chain databases if Start fails. defer func() { if done == nil { Chains.Close() @@ -157,9 +162,8 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { return _done } -const numWorkers = 8 - func engine(stop <-chan struct{}, done chan struct{}) { + // Always close all chains and remove lockfile on exit. defer func() { Chains.Close() if err := lockFile.Unlock(); err != nil { @@ -168,31 +172,56 @@ func engine(stop <-chan struct{}, done chan struct{}) { close(done) }() - // Launch workers + // eblocks is used to send new EBlocks to the workers for processing. eblocks := make(chan factom.EBlock) + + // eblocksWG is used to signal that all EBlocks for the current DBlock + // are done being processed. This is reused each DBlock. + eblocksWG := &sync.WaitGroup{} + + // stopWorkers may be called multiple times by any worker or this + // goroutine, but eblocks will only ever be closed once. var once sync.Once stopWorkers := func() { once.Do(func() { close(eblocks) }) } + + // Always stop all workers on exit. defer stopWorkers() + // dblock is declared here and reused so that the workers below can + // form a closure around it. var dblock factom.DBlock - wg := &sync.WaitGroup{} - launchWorkers(numWorkers, func() { - for eb := range eblocks { // Read until close(eblocks) - if err := Process(dblock.KeyMR, eb); err != nil { - log.Errorf("ChainID(%v): %v", eb.ChainID, err) - stopWorkers() // Tell workers and engine() to exit. + + // Launch workers to process new EBlocks. + numWorkers := runtime.NumCPU() + for i := 0; i < numWorkers; i++ { + go func() { + for eb := range eblocks { + if err := Process(dblock.KeyMR, eb); err != nil { + log.Errorf("ChainID(%v): %v", eb.ChainID, err) + // Tell workers and engine() to exit. + stopWorkers() + } + eblocksWG.Done() } - wg.Done() - } - }) + }() + } if !flag.IgnoreNewChains() && syncHeight < factomHeight { log.Infof("Searching for new FAT chains from block %v to %v...", syncHeight+1, factomHeight) } + + // synced tracks whether we have completed our first sync. var synced bool + + // retries tracks the number of times we have had to retry querying for + // the latest factom height. var retries int64 + + // scanTicker kicks off a new scan. scanTicker := time.NewTicker(scanInterval) + + // Factom Blockchain Scan Loop for { if !synced && syncHeight == factomHeight { synced = true @@ -200,82 +229,94 @@ func engine(stop <-chan struct{}, done chan struct{}) { log.Infof("Synced.") } - // Process all new DBlocks sequentially... + // Process all new DBlocks sequentially. for h := syncHeight + 1; h <= factomHeight; h++ { // Get DBlock. dblock = factom.DBlock{} - dblock.Header.Height = h + dblock.Height = h if err := dblock.Get(c); err != nil { log.Errorf("%#v.Get(c): %v", dblock, err) return } // Queue all EBlocks for processing. - wg.Add(len(dblock.EBlocks)) + eblocksWG.Add(len(dblock.EBlocks)) for _, eb := range dblock.EBlocks { eblocks <- eb } - wg.Wait() // Wait for all EBlocks to be processed. + // Wait for all EBlocks to be processed. + eblocksWG.Wait() - // Check for process errors... + // Check if any of the workers closed the eblocks + // channel to indicate a Process() error. select { case <-eblocks: // One or more of the workers had an error and // closed the eblocks channel. - // We cannot consider this DBlock completed, so - // we do not update sync height for all chains. + // Since we cannot consider this DBlock + // completed, we do not update sync height for + // any chains. return default: } - // DBlock completed. + // DBlock completed so update the sync height for all + // chains. setSyncHeight(h) if err := Chains.setSync(h, dblock.KeyMR); err != nil { return } + if flag.LogDebug && h%100 == 0 { + log.Debugf("Synced to block Height: %v KeyMR: %v", + h, dblock.KeyMR) + } + // Check that we haven't been told to stop. select { case <-stop: return default: } - - if flag.LogDebug && h%100 == 0 { - log.Debugf("Synced to block Height: %v KeyMR: %v", - h, dblock.KeyMR) - } } + // If we aren't yet synced, we want to immediately re-check the + // Factom Height as the Blockchain may have advanced in the + // time since we started the sync. if synced { - var pe factom.PendingEntries - if flag.DisablePending { - goto WAIT - } - // Get and apply any pending entries - if err := pe.Get(c); err != nil { - log.Error(err) - return - } - for i, j := 0, 0; i < len(pe); i = j { - e := pe[i] - if e.ChainID == nil { - // No more revealed entries - break + if !flag.DisablePending { + // Get and apply any pending entries. + var pe factom.PendingEntries + if err := pe.Get(c); err != nil { + log.Error(err) + return } - // Grab remaining entries with this chain ID. - for j = i + 1; j < len(pe); j++ { - chainID := pe[j].ChainID - if chainID == nil || *chainID != *e.ChainID { + + for i, j := 0, 0; i < len(pe); i = j { + e := pe[i] + // Unrevealed entries have no ChainID + // and are at the end of the slice. + if e.ChainID == nil { + // No more revealed entries. break } - } - if err := ProcessPending(pe[i:j]...); err != nil { - log.Error(err) - return + // Grab any subsequent entries with + // this ChainID. + for j = i + 1; j < len(pe); j++ { + chainID := pe[j].ChainID + if chainID == nil || *chainID != *e.ChainID { + break + } + } + // Process all pending entries for this + // chain. + if err := ProcessPending(pe[i:j]...); err != nil { + log.Error(err) + return + } } } - WAIT: + // Wait until the next scan tick or we're told to stop. select { case <-scanTicker.C: @@ -284,6 +325,7 @@ func engine(stop <-chan struct{}, done chan struct{}) { } } + // Check the Factom blockchain height but log and retry if this request fails. if err := updateFactomHeight(); err != nil { log.Error(err) if flag.FactomScanRetries > -1 && @@ -316,6 +358,7 @@ func setSyncHeight(sync uint32) { defer heightMtx.Unlock() syncHeight = sync } + func updateFactomHeight() error { // Get the current Factom Blockchain height. var heights factom.Heights @@ -328,18 +371,3 @@ func updateFactomHeight() error { factomHeight = heights.Entry return nil } - -func launchWorkers(num int, job func()) { - for i := 0; i < num; i++ { - go job() - } -} - -func createDir(path string) error { - if err := os.Mkdir(path, 0755); err != nil { - if !os.IsExist(err) { - return fmt.Errorf("os.Mkdir(%#v): %v", path, err) - } - } - return nil -} diff --git a/engine/process.go b/engine/process.go index 39ad058..9ee75cc 100644 --- a/engine/process.go +++ b/engine/process.go @@ -23,148 +23,234 @@ package engine import ( + "bytes" "time" + "crawshaw.io/sqlite" jrpc "github.com/AdamSLevy/jsonrpc2/v11" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" ) func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { - // Skip ignored chains or EBlocks for heights earlier than this chain's - // head height. chain := Chains.Get(eb.ChainID) - if chain.IsIgnored() { + + // Skip ignored chains and if we are ignoring new chain, also skip + // unknown chains. + if chain.IsIgnored() || + (flag.IgnoreNewChains() && chain.IsUnknown()) { return nil } if chain.IsUnknown() { - if flag.IgnoreNewChains() { - Chains.ignore(eb.ChainID) - return nil - } + // Attempt to open a new chain. var err error chain, err = OpenNew(c, dbKeyMR, eb) if err != nil { return err } + + // Ignore if the chain was not opened. if chain.IsUnknown() { Chains.ignore(eb.ChainID) return nil } + + // Fully sync the chain, so that it is up to date immediately. if err := chain.Sync(c); err != nil { return err } + + // Save the chain back into the map. Chains.set(chain.ID, chain, ChainStatusUnknown) return nil } + + // Ignore EBlocks earlier than the chain's current sync height. if eb.Height <= chain.Head.Height { return nil } - if chain.Pending.Entries != nil { - // Load any cached entries that are pending. + + // Rollback any pending entries on the chain. + if chain.Pending.OfficialSnapshot != nil { + // Load any cached entries that are pending and remove them + // from the cache. for i := range eb.Entries { e := &eb.Entries[i] - cached, ok := chain.Pending.Entries[*e.Hash] + + // Check if this entry is cached. + cachedE, ok := chain.Pending.Entries[*e.Hash] if !ok { continue } - // Save Timestamp established by EBlock - cached.Timestamp = e.Timestamp - *e = cached + + // Use official Timestamp established by EBlock. + cachedE.Timestamp = e.Timestamp + *e = cachedE + + // Delete the entry from the pending cache, rather than + // nil the entire map, because this allows the cache + // for various chains to grow according to how active + // they are. But this is not optimal for chains with + // only very occasional bursts of pending entries. + // + // A mechanism to eventually free rarely used pending + // entry maps would be a good improvement later. + delete(chain.Pending.Entries, *e.Hash) } - if err := chain.Pending.Sync(chain.Chain); err != nil { + + // Revert all of the pending transactions by applying the + // inverse of the changeset tracked by the session. + changeset := &bytes.Buffer{} + if err := chain.Pending.Session.Changeset(changeset); err != nil { return err } - session, err := chain.Conn.CreateSession("") - if err != nil { + inverse := bytes.NewBuffer(make([]byte, 0, changeset.Cap())) + if err := sqlite.ChangesetInvert(inverse, changeset); err != nil { return err } - if err := session.Attach(""); err != nil { + if err := chain.Conn.ChangesetApply( + inverse, nil, chain.conflictFn); err != nil { return err } - chain.Pending.MainSess = session + + // Clean up. + chain.Pending.Session.Delete() + chain.Pending.Session = nil + + chain.Pending.OfficialSnapshot.Free() + chain.Pending.OfficialSnapshot = nil } + + // prevStatus saves the initial ChainStatus so we can detect if the + // chain goes from Tracked to Issued. prevStatus := chain.ChainStatus + + // Apply this EBlock to the chain. if err := chain.Apply(c, dbKeyMR, eb); err != nil { return err } + + // Save the chain back into the map. Chains.set(chain.ID, chain, prevStatus) return nil } +func (chain Chain) conflictFn( + cType sqlite.ConflictType, _ sqlite.ChangesetIter) sqlite.ConflictAction { + chain.Log.Errorf("ChangesetApply Conflict: %v", cType) + return sqlite.SQLITE_CHANGESET_ABORT +} func ProcessPending(es ...factom.Entry) error { - e := es[0] // Deliberately panic if we are called with no entries. - if e.ChainID == nil { - return nil - } - chain := Chains.Get(e.ChainID) + chain := Chains.Get(es[0].ChainID) + // We can only apply pending entries to tracked chains. if !chain.IsTracked() { return nil } - if chain.Pending.Entries == nil { - if chain.Pending.Chain.Conn == nil { - if err := chain.Pending.Open(chain.Chain); err != nil { - return err - } - } else { - if err := chain.Pending.Sync(chain.Chain); err != nil { - return err - } + // Initialize Pending if there is no snapshot yet. + if chain.Pending.OfficialSnapshot == nil { + // Create the cache if it does not exist. + if chain.Pending.Entries == nil { + chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) } - chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) + // Take a snapshot of the official state and copy the current + // official Chain. + s, err := chain.Conn.CreateSnapshot("") + if err != nil { + return err + } + chain.Pending.OfficialSnapshot = s + chain.Pending.OfficialChain = chain.Chain - session, err := chain.Pending.Chain.Conn.CreateSession("") + // Start a new session so we can track all changes and later + // rollback all pending entries. + session, err := chain.Conn.CreateSession("") if err != nil { return err } if err := session.Attach(""); err != nil { return err } - chain.Pending.PendSess = session + chain.Pending.Session = session - // Small chance Identity is populated now but wasn't before... + // There is a chance the Identity is populated now but wasn't + // before, so update it now. if err := chain.Identity.Get(c); err != nil { - // A jrpc.Error indicates that the identity chain doesn't yet - // exist, which we tolerate. + // A jrpc.Error indicates that the identity chain + // doesn't yet exist, which we tolerate. if _, ok := err.(jrpc.Error); !ok { return err } } } - lenEntries := len(chain.Pending.Entries) + // startLenEntries tracks the initial size of our cache so we can + // detect if any new pending entries get applied. + startLenEntries := len(chain.Pending.Entries) + + // Apply any new pending entries. for _, e := range es { + // Ignore entries we have seen before. if _, ok := chain.Pending.Entries[*e.Hash]; ok { - // Ignore entries we have seen before. continue } + + // Load the Entry data. if err := e.Get(c); err != nil { return err } - // The timestamp will later be established by the next EBlock so use - // the current time for now. This is the time we first saw the pending - // entry. + + // The timestamp won't be established until the next EBlock so + // use the current time for now. e.Timestamp = time.Now() - if _, err := chain.Pending.Chain.ApplyEntry(e); err != nil { + + if _, err := chain.Chain.ApplyEntry(e); err != nil { return err } - chain.Pending.Entries[*e.Hash] = e // Cache the entry in memory. + + // Cache the entry. + chain.Pending.Entries[*e.Hash] = e } - if lenEntries == len(chain.Pending.Entries) { - // No new entries + + // Check if any no new entries were added. + if startLenEntries == len(chain.Pending.Entries) { return nil } + + // Save the chain back into the map. Chains.set(chain.ID, chain, chain.ChainStatus) return nil } -// ApplyPending applies any pending txs to conn. -func (chain *Chain) ApplyPending() { - if chain.Pending.Chain.Conn == nil || chain.Pending.MainSess != nil { - return +// Get returns a threadsafe connection to the database, and a function to +// release the connection back to the pool. If pending is true, the chain will +// reflect the state with pending entries applied. Otherwise the chain will +// reflect the official state after the most recent EBlock. +func (chain *Chain) Get(pending bool) func() { + // Pull a Conn off the Pool and set it as the main Conn. + conn := chain.Pool.Get(nil) + chain.Conn = conn + + // If pending or if there is no pending state, then use the chain as + // is, and just return a function that returns the conn to the pool. + if pending || chain.Pending.OfficialSnapshot == nil { + return func() { chain.Pool.Put(conn) } } - chain.Chain = chain.Pending.Chain + + // Use the official chain state with the conn from the Pool. + chain.Chain = chain.Pending.OfficialChain + chain.Conn = conn + + // Start a read transaction on the conn that reflects the official + // state. + endRead, err := conn.StartSnapshotRead(chain.Pending.OfficialSnapshot) + if err != nil { + panic(err) + } + + // Return a function that ends the read transaction and returns the + // conn to the Pool. + return func() { endRead(); chain.Pool.Put(conn) } } diff --git a/fat/chainid.go b/fat/chainid.go index b46c8bd..9c850ae 100644 --- a/fat/chainid.go +++ b/fat/chainid.go @@ -48,13 +48,18 @@ func NameIDs(tokenID string, issuerChainID *factom.Bytes32) []factom.Bytes { } } -// ChainID returns the chain ID for a given token ID and issuer Chain ID. -func ChainID(tokenID string, issuerChainID *factom.Bytes32) factom.Bytes32 { - return factom.ChainID(NameIDs(tokenID, issuerChainID)) +// ComputeChainID returns the ChainID for a given tokenID and issuerChainID. +func ComputeChainID(tokenID string, issuerChainID *factom.Bytes32) factom.Bytes32 { + return factom.ComputeChainID(NameIDs(tokenID, issuerChainID)) } -func TokenIssuer(nameIDs []factom.Bytes) (string, *factom.Bytes32) { +// TokenIssuer returns the tokenID and identityChainID for a given set of +// nameIDs. +// +// The caller must ensure that ValidTokenNameIDs(nameIDs) returns true or else +// TokenIssuer will return garbage data or may panic. +func TokenIssuer(nameIDs []factom.Bytes) (string, factom.Bytes32) { var identityChainID factom.Bytes32 copy(identityChainID[:], nameIDs[3]) - return string(nameIDs[1]), &identityChainID + return string(nameIDs[1]), identityChainID } diff --git a/flag/flag.go b/flag/flag.go index 94a8514..7ba9ef3 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -93,14 +93,14 @@ var ( "apitlscert": "", "apitlskey": "", - "s": "http://localhost:8088", + "s": "http://localhost:8088/v2", "factomdtimeout": 20 * time.Second, "factomduser": "", "factomdpassword": "", //"factomdcert": "", //"factomdtls": false, - "w": "http://localhost:8089", + "w": "http://localhost:8089/v2", "wallettimeout": 10 * time.Second, "walletuser": "", "walletpassword": "", @@ -194,7 +194,7 @@ var ( "-blacklist": complete.PredictAnything, "-ignorenewchains": complete.PredictNothing, - "-networkid": complete.PredictSet("main", "test", "local", "0x"), + "-networkid": complete.PredictSet("mainnet", "testnet", "localnet", "0x"), "-skipdbvalidation": complete.PredictNothing, } @@ -341,9 +341,10 @@ func Validate() { log.Debugf("-factomscanretries %v ", FactomScanRetries) debugPrintln() - log.Debugf("-s %#v", FactomClient.FactomdServer) + log.Debugf("-networkid %v", NetworkID) + log.Debugf("-s %q", FactomClient.FactomdServer) log.Debugf("-factomdtimeout %v ", FactomClient.Factomd.Timeout) - log.Debugf("-factomduser %#v", FactomClient.Factomd.User) + log.Debugf("-factomduser %q", FactomClient.Factomd.User) log.Debugf("-factomdpass %v ", factomdPassword) debugPrintln() diff --git a/go.mod b/go.mod index e4cd576..bbaf976 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 + github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d - github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a + github.com/Factom-Asset-Tokens/factom v0.0.0-20191002000329-c615e30e6f9b github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index a57f4a5..040973b 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBn github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 h1:McSW/pP7K0/Ucjig6AJwW7Khph/XOMYhSB8v3YxMBl4= github.com/AdamSLevy/jsonrpc2/v11 v11.3.2/go.mod h1:7fNjH6BXM0KVswWqj+K/mnOS8wiSke0sE8X46hS+nsc= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409 h1:s7+6wUZJvorkJWm6Eyifm4NDbmqGqJfFsZvtz416L2A= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 h1:eNbDxd8OsXjADVKdoDZEQwLqA08rzCUxvgnOiZAf6hs= @@ -15,8 +17,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a h1:Yc1KR8xLaJGV+lnyVDnazIhjY+VpmqwJ/cOY4CcJEmw= -github.com/Factom-Asset-Tokens/factom v0.0.0-20190911201853-7b283996f02a/go.mod h1:E1CRixabBfgR9Qb/ND2Q6Mqm9vCD3N9MH+sy60aj2x0= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191002000329-c615e30e6f9b h1:pEdccjvzWdb7fAqeZ8aRU1j9zX8O+92UBzhTIBeuSIw= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191002000329-c615e30e6f9b/go.mod h1:XbZ9AweH09WKgxrfPIEauJ5/2XL/zroNmDAbd3TdKXk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/srv/client.go b/srv/client.go index c34a36c..2808eab 100644 --- a/srv/client.go +++ b/srv/client.go @@ -26,7 +26,7 @@ import ( "fmt" "time" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jrpc "github.com/AdamSLevy/jsonrpc2/v12" ) // Client makes RPC requests to fatd's APIs. Client embeds a jsonrpc2.Client, diff --git a/srv/methods.go b/srv/methods.go index 3260c83..7b236ed 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -260,12 +260,9 @@ func getBalances(data json.RawMessage) interface{} { balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) - if params.HasIncludePending() { - chain.ApplyPending() - } - conn, put := chain.Get() + put := chain.Get(params.HasIncludePending()) defer put() - _, balance, err := addresses.SelectIDBalance(conn, params.Address) + _, balance, err := addresses.SelectIDBalance(chain.Conn, params.Address) if err != nil { panic(err) } @@ -470,7 +467,7 @@ func sendTransaction(data json.RawMessage) interface{} { return err } - var txID *factom.Bytes32 + var txID factom.Bytes32 if !params.DryRun { balance, err := flag.ECAdr.GetBalance(c) if err != nil { @@ -495,7 +492,7 @@ func sendTransaction(data json.RawMessage) interface{} { ChainID *factom.Bytes32 `json:"chainid"` TxID *factom.Bytes32 `json:"txid,omitempty"` Hash *factom.Bytes32 `json:"entryhash"` - }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} + }{ChainID: chain.ID, TxID: &txID, Hash: entry.Hash} } func attemptApplyFAT0Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) { // Validate tx @@ -666,11 +663,7 @@ func validate(data json.RawMessage, params Params) (*engine.Chain, func(), error if !chain.IsIssued() { return nil, nil, ErrorTokenNotFound } - if params.HasIncludePending() { - chain.ApplyPending() - } - conn, put := chain.Get() - chain.Conn = conn + put := chain.Get(params.HasIncludePending()) return &chain, put, nil } return nil, nil, nil diff --git a/srv/params.go b/srv/params.go index adb2153..8780932 100644 --- a/srv/params.go +++ b/srv/params.go @@ -77,7 +77,7 @@ func (p ParamsToken) ValidChainID() *factom.Bytes32 { if p.ChainID != nil { return p.ChainID } - chainID := fat.ChainID(p.TokenID, p.IssuerChainID) + chainID := fat.ComputeChainID(p.TokenID, p.IssuerChainID) p.ChainID = &chainID return p.ChainID } @@ -278,9 +278,12 @@ func (p *ParamsSendTransaction) IsValid() error { Timestamp: time.Now(), ChainID: p.ChainID, } - if _, err := p.entry.ComputeHash(); err != nil { + data, err := p.entry.MarshalBinary() + if err != nil { return jrpc.InvalidParams(err) } + hash := factom.ComputeEntryHash(data) + p.entry.Hash = &hash return nil } From 0c65dd0f8e9d3770ede0d01e7a6ecdd3d4e502d9 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 1 Oct 2019 16:15:07 -0800 Subject: [PATCH 068/124] fix(cli): Update default URLs for factomd and walletd --- cli/cmd/root.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 6c663a1..bbe3389 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -126,11 +126,11 @@ var apiFlags = func() *flag.FlagSet { flags.ParseErrorsWhitelist.UnknownFlags = true flags.StringVarP(&FATClient.FatdServer, "fatd", "d", - "localhost:8078", "scheme://host:port for fatd") + "http://localhost:8078", "scheme://host:port for fatd") flags.StringVarP(&FactomClient.FactomdServer, "factomd", "s", - "localhost:8088", "scheme://host:port for factomd") + factom.FactomdDefault, "scheme://host:port for factomd") flags.StringVarP(&FactomClient.WalletdServer, "walletd", "w", - "localhost:8089", "scheme://host:port for factom-walletd") + factom.WalletdDefault, "scheme://host:port for factom-walletd") flags.StringVar(&FATClient.User, "fatduser", "", "Basic HTTP Auth User for fatd") From e269f6014344ac4b7e7af7ac80576ad84b0c5240 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 1 Oct 2019 18:26:15 -0800 Subject: [PATCH 069/124] refactor(srv): Reduce memory allocation during send-transaction --- cli/cmd/issue.go | 2 +- cli/cmd/transact.go | 2 +- go.mod | 4 +++- go.sum | 4 ++-- srv/methods.go | 22 ++++++++++++---------- srv/params.go | 7 +++++-- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cli/cmd/issue.go b/cli/cmd/issue.go index 24a637c..342d7a0 100644 --- a/cli/cmd/issue.go +++ b/cli/cmd/issue.go @@ -268,7 +268,7 @@ func validateECAdrFlag(cmd *cobra.Command, _ []string) error { return nil } -func verifyECBalance(ec *factom.ECAddress, cost int8) { +func verifyECBalance(ec *factom.ECAddress, cost uint8) { vrbLog.Println("Checking EC balance... ") ecBalance, err := ec.GetBalance(FactomClient) if err != nil { diff --git a/cli/cmd/transact.go b/cli/cmd/transact.go index 8e44561..d79ba80 100644 --- a/cli/cmd/transact.go +++ b/cli/cmd/transact.go @@ -226,7 +226,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { var tx interface { Sign(...factom.RCDPrivateKey) MarshalEntry() error - Cost() (int8, error) + Cost() (uint8, error) } switch cmdType { case fat0.Type: diff --git a/go.mod b/go.mod index bbaf976..0bce0b2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d - github.com/Factom-Asset-Tokens/factom v0.0.0-20191002000329-c615e30e6f9b + github.com/Factom-Asset-Tokens/factom v0.0.0-20191002021226-1bb51d430207 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 @@ -25,3 +25,5 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 + +//replace github.com/Factom-Asset-Tokens/factom => ../factom diff --git a/go.sum b/go.sum index 040973b..e118633 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191002000329-c615e30e6f9b h1:pEdccjvzWdb7fAqeZ8aRU1j9zX8O+92UBzhTIBeuSIw= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191002000329-c615e30e6f9b/go.mod h1:XbZ9AweH09WKgxrfPIEauJ5/2XL/zroNmDAbd3TdKXk= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191002021226-1bb51d430207 h1:YWqkcnkOGv/AoW+G/qJpOPAhi9i6udc2e4UJMGXweAM= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191002021226-1bb51d430207/go.mod h1:XbZ9AweH09WKgxrfPIEauJ5/2XL/zroNmDAbd3TdKXk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/srv/methods.go b/srv/methods.go index 7b236ed..d381b35 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -467,32 +467,34 @@ func sendTransaction(data json.RawMessage) interface{} { return err } - var txID factom.Bytes32 + var txID *factom.Bytes32 if !params.DryRun { balance, err := flag.ECAdr.GetBalance(c) if err != nil { panic(err) } - cost, err := entry.Cost() - if err != nil { - rerr := ErrorInvalidTransaction - rerr.Data = err.Error() - return rerr - } + cost, _ := entry.Cost() if balance < uint64(cost) { return ErrorNoEC } - txID, err = entry.ComposeCreate(c, flag.EsAdr) - if err != nil { + txID = new(factom.Bytes32) + var commit []byte + commit, *txID = factom.GenerateCommit(flag.EsAdr, + params.Raw, entry.Hash, false) + if err := c.Commit(commit); err != nil { panic(err) } + if err := c.Reveal(params.Raw); err != nil { + panic(err) + } + } return struct { ChainID *factom.Bytes32 `json:"chainid"` TxID *factom.Bytes32 `json:"txid,omitempty"` Hash *factom.Bytes32 `json:"entryhash"` - }{ChainID: chain.ID, TxID: &txID, Hash: entry.Hash} + }{ChainID: chain.ID, TxID: txID, Hash: entry.Hash} } func attemptApplyFAT0Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) { // Validate tx diff --git a/srv/params.go b/srv/params.go index 8780932..7c03b04 100644 --- a/srv/params.go +++ b/srv/params.go @@ -255,12 +255,13 @@ type ParamsSendTransaction struct { func (p *ParamsSendTransaction) IsValid() error { if p.Raw != nil { - if p.ExtIDs != nil || p.Content != nil || p.ParamsToken != (ParamsToken{}) { + if p.ExtIDs != nil || p.Content != nil || + p.ParamsToken != (ParamsToken{}) { return jrpc.InvalidParams( `"raw cannot be used with "content" or "extids"`) } if err := p.entry.UnmarshalBinary(p.Raw); err != nil { - return jrpc.InvalidParams(err) + return jrpc.InvalidParams(err.Error()) } p.entry.Timestamp = time.Now() p.ChainID = p.entry.ChainID @@ -278,12 +279,14 @@ func (p *ParamsSendTransaction) IsValid() error { Timestamp: time.Now(), ChainID: p.ChainID, } + data, err := p.entry.MarshalBinary() if err != nil { return jrpc.InvalidParams(err) } hash := factom.ComputeEntryHash(data) p.entry.Hash = &hash + p.Raw = data return nil } From 87af3f5fef18049a7caef1f2dd7b3553401acc46 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 6 Oct 2019 15:33:03 -0800 Subject: [PATCH 070/124] feat(all): Use context.Context throughout to coordinate cancelation for clean shutdown Update to the latest jsonrpc2/v12@develop and latest factom@develop in order to take full advantage of context.Context. All server requests now have a timeout that prevent resource intensive SQL queries from ever hijacking fatd. Additionally if the client cancels a request all work on the request will stop. When it is time to shutdown, whether due to an error from the engine or from a SIGINT then the server will properly load-shed, and all open network requests or db queries will be canceled immediately. --- cli/cmd/getbalance.go | 16 ++++--- cli/cmd/getchains.go | 8 ++-- cli/cmd/gettransactions.go | 9 ++-- cli/cmd/issue.go | 44 +++++++++-------- cli/cmd/predict.go | 97 +++++++++++++++----------------------- cli/cmd/root.go | 4 +- cli/cmd/transact.go | 24 ++++++---- db/chain.go | 19 ++++---- engine/chain.go | 48 ++++++++++--------- engine/chainmap.go | 7 +-- engine/engine.go | 73 +++++++++++++++++++--------- engine/process.go | 15 +++--- fat/entry.go | 7 ++- fat/fat0/transaction.go | 4 +- fat/fat1/transaction.go | 4 +- fat/issuance.go | 4 +- flag/flag.go | 3 +- flag/predict.go | 50 +++++++++----------- go.mod | 5 +- go.sum | 12 ++--- main.go | 24 ++++++---- srv/client.go | 10 ++-- srv/errors.go | 14 +++--- srv/methods.go | 85 ++++++++++++++++++--------------- srv/params.go | 37 ++++++++------- srv/srv.go | 17 ++++--- 26 files changed, 341 insertions(+), 299 deletions(-) diff --git a/cli/cmd/getbalance.go b/cli/cmd/getbalance.go index f8ded52..8786367 100644 --- a/cli/cmd/getbalance.go +++ b/cli/cmd/getbalance.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "fmt" "math" @@ -103,8 +104,8 @@ func getBalance(cmd *cobra.Command, _ []string) { for _, adr := range addresses { params.Address = &adr var balances srv.ResultGetBalances - if err := FATClient.Request("get-balances", params, - &balances); err != nil { + if err := FATClient.Request(context.Background(), + "get-balances", params, &balances); err != nil { errLog.Fatal(err) } fmt.Printf("%v:", adr) @@ -122,7 +123,8 @@ func getBalance(cmd *cobra.Command, _ []string) { vrbLog.Printf("Fetching token chain details... %v", paramsToken.ChainID) params := srv.ParamsToken{ChainID: paramsToken.ChainID} var stats srv.ResultGetStats - if err := FATClient.Request("get-stats", params, &stats); err != nil { + if err := FATClient.Request(context.Background(), + "get-stats", params, &stats); err != nil { errLog.Fatal(err) } switch stats.Issuance.Type { @@ -134,8 +136,8 @@ func getBalance(cmd *cobra.Command, _ []string) { for _, adr := range addresses { params.Address = &adr var balance uint64 - if err := FATClient.Request("get-balance", params, - &balance); err != nil { + if err := FATClient.Request(context.Background(), + "get-balance", params, &balance); err != nil { errLog.Fatal(err) } fmt.Println(adr, balance) @@ -149,8 +151,8 @@ func getBalance(cmd *cobra.Command, _ []string) { for _, adr := range addresses { params.Address = &adr var balance fat1.NFTokens - if err := FATClient.Request("get-nf-balance", params, - &balance); err != nil { + if err := FATClient.Request(context.Background(), + "get-nf-balance", params, &balance); err != nil { errLog.Fatal(err) } fmt.Println(adr, balance) diff --git a/cli/cmd/getchains.go b/cli/cmd/getchains.go index 322fa87..4180f81 100644 --- a/cli/cmd/getchains.go +++ b/cli/cmd/getchains.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "fmt" "github.com/Factom-Asset-Tokens/factom" @@ -99,8 +100,8 @@ func getChains(_ *cobra.Command, _ []string) { if len(chainIDs) == 0 { vrbLog.Println("Fetching list of issued token chains...") var chains []srv.ParamsToken - if err := FATClient.Request("get-daemon-tokens", nil, - &chains); err != nil { + if err := FATClient.Request(context.Background(), + "get-daemon-tokens", nil, &chains); err != nil { errLog.Fatal(err) } for _, chain := range chains { @@ -118,7 +119,8 @@ Token ID: %q params := srv.ParamsToken{ChainID: &chainID, IncludePending: paramsToken.IncludePending} var stats srv.ResultGetStats - if err := FATClient.Request("get-stats", params, &stats); err != nil { + if err := FATClient.Request(context.Background(), + "get-stats", params, &stats); err != nil { errLog.Fatal(err) } printStats(&chainID, stats) diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index 72e2b56..e67d955 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "encoding/json" "fmt" "strings" @@ -185,8 +186,8 @@ func getTxs(_ *cobra.Command, _ []string) { for i := range result { result[i].Tx = &json.RawMessage{} } - if err := FATClient.Request("get-transactions", - paramsGetTxs, &result); err != nil { + if err := FATClient.Request(context.Background(), + "get-transactions", paramsGetTxs, &result); err != nil { errLog.Fatal(err) } for _, result := range result { @@ -201,8 +202,8 @@ func getTxs(_ *cobra.Command, _ []string) { for _, txID := range transactionIDs { vrbLog.Printf("Fetching tx details... %v", txID) params.Hash = &txID - if err := FATClient.Request("get-transaction", - params, &result); err != nil { + if err := FATClient.Request(context.Background(), + "get-transaction", params, &result); err != nil { errLog.Fatal(err) } printTx(result) diff --git a/cli/cmd/issue.go b/cli/cmd/issue.go index 342d7a0..f429a3c 100644 --- a/cli/cmd/issue.go +++ b/cli/cmd/issue.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "encoding/json" "fmt" "strings" @@ -31,7 +32,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/srv" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/posener/complete" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -153,8 +154,9 @@ var issueCmplCmd = complete.Command{ } var ( - missingChainHeadErr = jrpc.Error{Code: -32009, Message: "Missing Chain Head"} - newChainInProcessListErr = jrpc.Error{Message: "new chain in process list"} + missingChainHeadErr = jsonrpc2.Error{ + Code: -32009, Message: "Missing Chain Head"} + newChainInProcessListErr = jsonrpc2.Error{Message: "new chain in process list"} first factom.Entry chainExists bool @@ -207,8 +209,8 @@ func validateIssueFlags(cmd *cobra.Command, args []string) error { if !force { vrbLog.Println("Checking chain existence...") eb := factom.EBlock{ChainID: paramsToken.ChainID} - if err := eb.GetChainHead(FactomClient); err != nil { - rpcErr, _ := err.(jrpc.Error) + if err := eb.GetChainHead(context.Background(), FactomClient); err != nil { + rpcErr, _ := err.(jsonrpc2.Error) if rpcErr != missingChainHeadErr && rpcErr != newChainInProcessListErr { // If err was anything other than the missingChainHeadErr... @@ -223,9 +225,10 @@ func validateIssueFlags(cmd *cobra.Command, args []string) error { vrbLog.Println("Checking token chain status...") params := srv.ParamsToken{ChainID: paramsToken.ChainID} var stats srv.ResultGetStats - if err := FATClient.Request("get-stats", params, &stats); err != nil { - rpcErr, _ := err.(jrpc.Error) - if rpcErr != *srv.ErrorTokenNotFound { + if err := FATClient.Request(context.Background(), + "get-stats", params, &stats); err != nil { + rpcErr, _ := err.(jsonrpc2.Error) + if rpcErr != srv.ErrorTokenNotFound { errLog.Fatal(err) } } else { @@ -257,9 +260,9 @@ func validateECAdrFlag(cmd *cobra.Command, _ []string) error { var err error if ecEsAdr.Es == zero { vrbLog.Println("Fetching secret address...", ecEsAdr.EC) - ecEsAdr.Es, err = ecEsAdr.EC.GetEsAddress(FactomClient) + ecEsAdr.Es, err = ecEsAdr.EC.GetEsAddress(context.Background(), FactomClient) if err != nil { - if err, ok := err.(jrpc.Error); ok { + if err, ok := err.(jsonrpc2.Error); ok { errLog.Fatal(err.Data, ecEsAdr.EC) } errLog.Fatal(err) @@ -270,7 +273,7 @@ func validateECAdrFlag(cmd *cobra.Command, _ []string) error { func verifyECBalance(ec *factom.ECAddress, cost uint8) { vrbLog.Println("Checking EC balance... ") - ecBalance, err := ec.GetBalance(FactomClient) + ecBalance, err := ec.GetBalance(context.Background(), FactomClient) if err != nil { errLog.Fatal(err) } @@ -284,11 +287,12 @@ func verifySK1Key(sk1 *factom.SK1Key, idChainID *factom.Bytes32) { vrbLog.Printf("Fetching Identity Chain...") var identity factom.Identity identity.ChainID = idChainID - if err := identity.Get(FactomClient); err != nil { - rpcErr, _ := err.(jrpc.Error) + if err := identity.Get(context.Background(), FactomClient); err != nil { + rpcErr, _ := err.(jsonrpc2.Error) if rpcErr == newChainInProcessListErr { - errLog.Fatalf("New identity chain %v is in process list. "+ - "Wait ~10 mins.\n", idChainID) + errLog.Fatalf( + "New identity chain %v is in process list. Wait ~10 mins.\n", + idChainID) } if rpcErr == missingChainHeadErr { errLog.Fatalf("Identity Chain does not exist: %v", idChainID) @@ -296,9 +300,9 @@ func verifySK1Key(sk1 *factom.SK1Key, idChainID *factom.Bytes32) { errLog.Fatal(err) } vrbLog.Println("Verifying SK1 Key... ") - if identity.ID1 != sk1.ID1Key() { - errLog.Fatal("--sk1 is not the secret key corresponding to " + - "the ID1Key declared in the Identity Chain.") + if *identity.ID1 != sk1.ID1Key() { + errLog.Fatal( + "--sk1 is not the secret key corresponding to the ID1Key declared in the Identity Chain.") } } @@ -317,7 +321,7 @@ func issueChain(_ *cobra.Command, _ []string) { } vrbLog.Println("Submitting the Chain Creation Entry to the Factom blockchain...") - txID, err := first.ComposeCreate(FactomClient, ecEsAdr.Es) + txID, err := first.ComposeCreate(context.Background(), FactomClient, ecEsAdr.Es) if err != nil { errLog.Fatal(err) } @@ -338,7 +342,7 @@ func issueToken(_ *cobra.Command, _ []string) { vrbLog.Println( "Submitting the Token Initialization Entry to the Factom blockchain...") - txID, err := Issuance.ComposeCreate(FactomClient, ecEsAdr.Es) + txID, err := Issuance.ComposeCreate(context.Background(), FactomClient, ecEsAdr.Es) if err != nil { errLog.Fatal(err) } diff --git a/cli/cmd/predict.go b/cli/cmd/predict.go index 59c8b66..4b010cc 100644 --- a/cli/cmd/predict.go +++ b/cli/cmd/predict.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "log" "os" "strings" @@ -55,38 +56,6 @@ func parseAPIFlags() error { return nil } -var PredictFAAddresses complete.PredictFunc = func(args complete.Args) []string { - if len(args.Last) > 52 { - return nil - } - if err := parseAPIFlags(); err != nil { - return nil - } - adrs, err := FactomClient.GetFAAddresses() - if err != nil { - logErr(err) - return nil - } - completed := make(map[factom.FAAddress]struct{}, len(args.Completed)-1) - for _, arg := range args.Completed[1:] { - var adr factom.FAAddress - if adr.Set(arg) != nil { - continue - } - completed[adr] = struct{}{} - } - adrStrs := make([]string, len(adrs)-len(completed)) - var i int - for _, adr := range adrs { - if _, ok := completed[adr]; ok { - continue - } - adrStrs[i] = adr.String() - i++ - } - return adrStrs -} - func PredictAppend(predict complete.PredictFunc, suffix string) complete.PredictFunc { return func(args complete.Args) []string { predictions := predict(args) @@ -97,38 +66,45 @@ func PredictAppend(predict complete.PredictFunc, suffix string) complete.Predict } } -var PredictECAddresses complete.PredictFunc = func(args complete.Args) []string { - if len(args.Last) > 52 { - return nil - } - if err := parseAPIFlags(); err != nil { - return nil - } - adrs, err := FactomClient.GetECAddresses() - if err != nil { - logErr(err) - return nil - } - completed := make(map[factom.ECAddress]struct{}, len(args.Completed)-1) - for _, arg := range args.Completed[1:] { - var adr factom.ECAddress - if adr.Set(arg) != nil { - continue +func PredictAddressesFunc(fa bool) complete.PredictFunc { + return func(args complete.Args) []string { + // Check if the argument we are completing already exceeds the + // length of an address. + if len(args.Last) > 52 { + return nil } - completed[adr] = struct{}{} - } - adrStrs := make([]string, len(adrs)-len(completed)) - var i int - for _, adr := range adrs { - if _, ok := completed[adr]; ok { - continue + + if err := parseAPIFlags(); err != nil { + return nil } - adrStrs[i] = adr.String() - i++ + + fss, ess, err := FactomClient.GetPrivateAddresses(context.Background()) + if err != nil { + logErr(err) + return nil + } + + // Return only the public addresses that we need. + var adrStrs []string + if fa { + adrStrs = make([]string, len(fss)) + for i, fs := range fss { + adrStrs[i] = fs.FAAddress().String() + } + } else { + adrStrs = make([]string, len(ess)) + for i, es := range ess { + adrStrs[i] = es.ECAddress().String() + } + } + return adrStrs } - return adrStrs + } +var PredictFAAddresses = PredictAddressesFunc(true) +var PredictECAddresses = PredictAddressesFunc(false) + var PredictChainIDs complete.PredictFunc = func(args complete.Args) []string { if len(args.Last) > 64 { return nil @@ -137,7 +113,8 @@ var PredictChainIDs complete.PredictFunc = func(args complete.Args) []string { return nil } var chains []srv.ParamsToken - if err := FATClient.Request("get-daemon-tokens", nil, &chains); err != nil { + if err := FATClient.Request(context.Background(), + "get-daemon-tokens", nil, &chains); err != nil { logErr(err) return nil } diff --git a/cli/cmd/root.go b/cli/cmd/root.go index bbe3389..6b3476f 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "fmt" "io/ioutil" "log" @@ -400,7 +401,8 @@ func printVersions() { fmt.Printf("fat-cli: %v\n", Revision) vrbLog.Println("Fetching fatd properties...") var properties srv.ResultGetDaemonProperties - if err := FATClient.Request("get-daemon-properties", nil, &properties); err != nil { + if err := FATClient.Request(context.Background(), + "get-daemon-properties", nil, &properties); err != nil { errLog.Fatal(err) } fmt.Printf("fatd: %v\n", properties.FatdVersion) diff --git a/cli/cmd/transact.go b/cli/cmd/transact.go index d79ba80..c0a88f5 100644 --- a/cli/cmd/transact.go +++ b/cli/cmd/transact.go @@ -23,6 +23,7 @@ package cmd import ( + "context" "encoding/json" "fmt" "math" @@ -33,7 +34,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/Factom-Asset-Tokens/fatd/srv" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/posener/complete" "github.com/spf13/cobra" ) @@ -200,9 +201,9 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { if !ok { var err error vrbLog.Println("Fetching secret address...", fa) - fs, err = fa.GetFsAddress(FactomClient) + fs, err = fa.GetFsAddress(context.Background(), FactomClient) if err != nil { - if err, ok := err.(jrpc.Error); ok { + if err, ok := err.(jsonrpc2.Error); ok { errLog.Fatal(err.Data, fa) } errLog.Fatal(err) @@ -252,7 +253,8 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { vrbLog.Println("Checking token chain status...") params := srv.ParamsToken{ChainID: paramsToken.ChainID} var stats srv.ResultGetStats - if err := FATClient.Request("get-stats", params, &stats); err != nil { + if err := FATClient.Request(context.Background(), + "get-stats", params, &stats); err != nil { errLog.Fatal(err) } // Verify we are using the right command for this token type. @@ -267,7 +269,8 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { vrbLog.Println("Checking FAT Token balance...", adr) paramsGetBalance.Address = &adr var balance uint64 - if err := FATClient.Request("get-balance", + if err := FATClient.Request(context.Background(), + "get-balance", paramsGetBalance, &balance); err != nil { errLog.Fatal(err) } @@ -292,8 +295,8 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { vrbLog.Println("Checking FAT NF Token ownership...", adr) params.Address = &adr var balance fat1.NFTokens - if err := FATClient.Request("get-nf-balance", - params, &balance); err != nil { + if err := FATClient.Request(context.Background(), + "get-nf-balance", params, &balance); err != nil { errLog.Fatal(err) } if err := balance.ContainsAll(fat1Tx.Inputs[adr]); err != nil { @@ -328,12 +331,13 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { params := srv.ParamsGetNFToken{ParamsToken: params} for tknID := range fat1Tx.Inputs.AllNFTokens() { params.NFTokenID = &tknID - err := FATClient.Request("get-nf-token", params, nil) + err := FATClient.Request(context.Background(), + "get-nf-token", params, nil) if err == nil { errLog.Fatalf("invalid coinbase transaction: NFTokenID (%v) already exists", tknID) } - rpcErr, _ := err.(jrpc.Error) + rpcErr, _ := err.(jsonrpc2.Error) if rpcErr.Code != srv.ErrorTokenNotFound.Code { errLog.Fatal(err) } @@ -363,7 +367,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { vrbLog.Printf("Submitting the %v Transaction Entry to the Factom blockchain...", cmdType) - txID, err := entry.ComposeCreate(FactomClient, ecEsAdr.Es) + txID, err := entry.ComposeCreate(context.Background(), FactomClient, ecEsAdr.Es) if err != nil { errLog.Fatal(err) } diff --git a/db/chain.go b/db/chain.go index e425a38..b18bf87 100644 --- a/db/chain.go +++ b/db/chain.go @@ -23,6 +23,7 @@ package db import ( + "context" "fmt" "io/ioutil" "os" @@ -78,7 +79,7 @@ type Chain struct { } // dbPath must be path ending in os.Separator -func OpenNew(dbPath string, +func OpenNew(ctx context.Context, dbPath string, dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, identity factom.Identity) (chain Chain, err error) { fname := eb.ChainID.String() + dbFileExtension @@ -100,7 +101,7 @@ func OpenNew(dbPath string, return } - chain.Conn, chain.Pool, err = OpenConnPool(dbPath + fname) + chain.Conn, chain.Pool, err = OpenConnPool(ctx, dbPath+fname) if err != nil { return } @@ -142,8 +143,8 @@ func OpenNew(dbPath string, return } -func Open(dbPath, fname string) (chain Chain, err error) { - chain.Conn, chain.Pool, err = OpenConnPool(dbPath + fname) +func Open(ctx context.Context, dbPath, fname string) (chain Chain, err error) { + chain.Conn, chain.Pool, err = OpenConnPool(ctx, dbPath+fname) if err != nil { return } @@ -159,7 +160,7 @@ func Open(dbPath, fname string) (chain Chain, err error) { return } -func OpenAll(dbPath string) (chains []Chain, err error) { +func OpenAll(ctx context.Context, dbPath string) (chains []Chain, err error) { log = _log.New("pkg", "db") defer func() { if err != nil { @@ -184,7 +185,7 @@ func OpenAll(dbPath string) (chains []Chain, err error) { continue } log.Debugf("Loading chain: %v", chainID) - chain, err := Open(dbPath, fname) + chain, err := Open(ctx, dbPath, fname) if err != nil { return nil, err } @@ -211,8 +212,9 @@ func fnameToChainID(fname string) (*factom.Bytes32, error) { return chainID, nil } -func OpenConnPool(dbURI string) (conn *sqlite.Conn, pool *sqlitex.Pool, - err error) { +func OpenConnPool(ctx context.Context, dbURI string) ( + conn *sqlite.Conn, pool *sqlitex.Pool, err error) { + const baseFlags = sqlite.SQLITE_OPEN_WAL | sqlite.SQLITE_OPEN_URI | sqlite.SQLITE_OPEN_NOMUTEX @@ -228,6 +230,7 @@ func OpenConnPool(dbURI string) (conn *sqlite.Conn, pool *sqlitex.Pool, } } }() + conn.SetInterrupt(ctx.Done()) if err = validateOrApplySchema(conn, chainDBSchema); err != nil { return } diff --git a/engine/chain.go b/engine/chain.go index e83ae0b..b9e7c66 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -23,11 +23,12 @@ package engine import ( + "context" "fmt" "crawshaw.io/sqlite" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/db" @@ -56,15 +57,15 @@ func (chain Chain) String() string { chain.Identity, chain.Issuance) } -func OpenNew(c *factom.Client, +func OpenNew(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) (chain Chain, err error) { - if err := eb.Get(c); err != nil { - return chain, fmt.Errorf("%#v.Get(c): %v", eb, err) + if err := eb.Get(context.TODO(), c); err != nil { + return chain, fmt.Errorf("%#v.Get(): %v", eb, err) } // Load first entry of new chain. first := &eb.Entries[0] - if err := first.Get(c); err != nil { - return chain, fmt.Errorf("%#v.Get(c): %v", first, err) + if err := first.Get(context.TODO(), c); err != nil { + return chain, fmt.Errorf("%#v.Get(): %v", first, err) } if !eb.IsFirst() { return @@ -79,19 +80,20 @@ func OpenNew(c *factom.Client, var identity factom.Identity identity.ChainID = new(factom.Bytes32) _, *identity.ChainID = fat.TokenIssuer(nameIDs) - if err = identity.Get(c); err != nil { - // A jrpc.Error indicates that the identity chain + if err = identity.Get(context.TODO(), c); err != nil { + // A jsonrpc2.Error indicates that the identity chain // doesn't yet exist, which we tolerate. - if _, ok := err.(jrpc.Error); !ok { + if _, ok := err.(jsonrpc2.Error); !ok { return } } - if err := eb.GetEntries(c); err != nil { - return chain, fmt.Errorf("%#v.GetEntries(c): %v", eb, err) + if err := eb.GetEntries(context.TODO(), c); err != nil { + return chain, fmt.Errorf("%#v.GetEntries(): %v", eb, err) } - chain.Chain, err = db.OpenNew(flag.DBPath, dbKeyMR, eb, flag.NetworkID, identity) + chain.Chain, err = db.OpenNew(ctx, + flag.DBPath, dbKeyMR, eb, flag.NetworkID, identity) if err != nil { return chain, fmt.Errorf("db.OpenNew(): %v", err) } @@ -103,8 +105,9 @@ func OpenNew(c *factom.Client, return } -func (chain *Chain) OpenNewByChainID(c *factom.Client, chainID *factom.Bytes32) error { - eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(c) +func (chain *Chain) OpenNewByChainID(ctx context.Context, + c *factom.Client, chainID *factom.Bytes32) error { + eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(context.TODO(), c) if err != nil { return fmt.Errorf("factom.EBlock{}.GetPrevAll(): %v", err) } @@ -113,12 +116,12 @@ func (chain *Chain) OpenNewByChainID(c *factom.Client, chainID *factom.Bytes32) // Get DBlock Timestamp and KeyMR var dblock factom.DBlock dblock.Height = first.Height - if err := dblock.Get(c); err != nil { + if err := dblock.Get(context.TODO(), c); err != nil { return fmt.Errorf("factom.DBlock{}.Get(): %v", err) } first.SetTimestamp(dblock.Timestamp) - *chain, err = OpenNew(c, dblock.KeyMR, first) + *chain, err = OpenNew(ctx, c, dblock.KeyMR, first) if err != nil { return err } @@ -131,7 +134,8 @@ func (chain *Chain) OpenNewByChainID(c *factom.Client, chainID *factom.Bytes32) } func (chain *Chain) Sync(c *factom.Client) error { - eblocks, err := factom.EBlock{ChainID: chain.ID}.GetPrevUpTo(c, *chain.Head.KeyMR) + eblocks, err := factom.EBlock{ChainID: chain.ID}. + GetPrevUpTo(context.TODO(), c, *chain.Head.KeyMR) if err != nil { return fmt.Errorf("factom.EBlock{}.GetPrevUpTo(): %v", err) } @@ -145,7 +149,7 @@ func (chain *Chain) SyncEBlocks(c *factom.Client, ebs []factom.EBlock) error { // Get DBlock Timestamp and KeyMR var dblock factom.DBlock dblock.Height = eb.Height - if err := dblock.Get(c); err != nil { + if err := dblock.Get(context.TODO(), c); err != nil { return fmt.Errorf("factom.DBlock{}.Get(): %v", err) } eb.SetTimestamp(dblock.Timestamp) @@ -160,15 +164,15 @@ func (chain *Chain) SyncEBlocks(c *factom.Client, ebs []factom.EBlock) error { func (chain *Chain) Apply(c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) error { // Get Identity each time in case it wasn't populated before. - if err := chain.Identity.Get(c); err != nil { - // A jrpc.Error indicates that the identity chain doesn't yet + if err := chain.Identity.Get(context.TODO(), c); err != nil { + // A jsonrpc2.Error indicates that the identity chain doesn't yet // exist, which we tolerate. - if _, ok := err.(jrpc.Error); !ok { + if _, ok := err.(jsonrpc2.Error); !ok { return err } } // Get all entry data. - if err := eb.GetEntries(c); err != nil { + if err := eb.GetEntries(context.TODO(), c); err != nil { return err } if err := chain.Chain.Apply(dbKeyMR, eb); err != nil { diff --git a/engine/chainmap.go b/engine/chainmap.go index 5bb869a..e6938ee 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -23,6 +23,7 @@ package engine import ( + "context" "fmt" "math" "sync" @@ -124,8 +125,8 @@ func (cm ChainMap) Close() { // loadChains loads all chains from the database that are not blacklisted, and // syncs them. Any whitelisted chains that are not previously tracked are // synced. The lowest sync height among all chain databases is returned. -func loadChains() (syncHeight uint32, err error) { - dbChains, err := db.OpenAll(flag.DBPath) +func loadChains(ctx context.Context) (syncHeight uint32, err error) { + dbChains, err := db.OpenAll(ctx, flag.DBPath) if err != nil { return } @@ -199,7 +200,7 @@ func loadChains() (syncHeight uint32, err error) { if chain.IsIgnored() || chain.Chain.Conn != nil { continue } - if err = chain.OpenNewByChainID(c, &id); err != nil { + if err = chain.OpenNewByChainID(ctx, c, &id); err != nil { return } Chains.trackedIDs = append(Chains.trackedIDs, chain.ID) diff --git a/engine/engine.go b/engine/engine.go index b538bfe..e9d2617 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -23,6 +23,7 @@ package engine import ( + "context" "fmt" "os" "runtime" @@ -42,6 +43,14 @@ var ( lockFile lockfile.Lockfile ) +func runIfNotDone(ctx context.Context, f func()) { + select { + case <-ctx.Done(): + default: + f() + } +} + const ( scanInterval = 30 * time.Second ) @@ -51,7 +60,7 @@ const ( // finish processing the current DBlock, cleanup and close state, all // goroutines will exit, and done will be closed. If the done channel is closed // before the stop channel is closed, an error occurred. -func Start(stop <-chan struct{}) (done <-chan struct{}) { +func Start(ctx context.Context) (done <-chan struct{}) { log = _log.New("pkg", "engine") // Try to create the main and pending database directories, in case @@ -85,14 +94,18 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { }() // Verify Factom Blockchain NetworkID... - if err := updateFactomHeight(); err != nil { - log.Error(err) + if err := updateFactomHeight(ctx); err != nil { + runIfNotDone(ctx, func() { + log.Error(err) + }) return } var dblock factom.DBlock dblock.Height = factomHeight - if err := dblock.Get(c); err != nil { - log.Errorf("dblock.Get(): %v", err) + if err := dblock.Get(ctx, c); err != nil { + runIfNotDone(ctx, func() { + log.Errorf("dblock.Get(): %v", err) + }) return } if dblock.NetworkID != flag.NetworkID { @@ -106,9 +119,11 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { if flag.SkipDBValidation { log.Warn("Skipping database validation...") } - syncHeight, err = loadChains() + syncHeight, err = loadChains(ctx) if err != nil { - log.Error(err) + runIfNotDone(ctx, func() { + log.Error(err) + }) return } // Always close all chain databases if Start fails. @@ -158,11 +173,11 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { } _done := make(chan struct{}) - go engine(stop, _done) + go engine(ctx, _done) return _done } -func engine(stop <-chan struct{}, done chan struct{}) { +func engine(ctx context.Context, done chan struct{}) { // Always close all chains and remove lockfile on exit. defer func() { Chains.Close() @@ -196,8 +211,11 @@ func engine(stop <-chan struct{}, done chan struct{}) { for i := 0; i < numWorkers; i++ { go func() { for eb := range eblocks { - if err := Process(dblock.KeyMR, eb); err != nil { - log.Errorf("ChainID(%v): %v", eb.ChainID, err) + if err := Process(ctx, dblock.KeyMR, eb); err != nil { + runIfNotDone(ctx, func() { + log.Errorf("ChainID(%v): %v", + eb.ChainID, err) + }) // Tell workers and engine() to exit. stopWorkers() } @@ -234,8 +252,10 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Get DBlock. dblock = factom.DBlock{} dblock.Height = h - if err := dblock.Get(c); err != nil { - log.Errorf("%#v.Get(c): %v", dblock, err) + if err := dblock.Get(ctx, c); err != nil { + runIfNotDone(ctx, func() { + log.Errorf("%#v.Get(): %v", dblock, err) + }) return } @@ -264,6 +284,9 @@ func engine(stop <-chan struct{}, done chan struct{}) { // chains. setSyncHeight(h) if err := Chains.setSync(h, dblock.KeyMR); err != nil { + runIfNotDone(ctx, func() { + log.Errorf("Chains.setSync(): %v", err) + }) return } @@ -274,7 +297,7 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Check that we haven't been told to stop. select { - case <-stop: + case <-ctx.Done(): return default: } @@ -287,8 +310,12 @@ func engine(stop <-chan struct{}, done chan struct{}) { if !flag.DisablePending { // Get and apply any pending entries. var pe factom.PendingEntries - if err := pe.Get(c); err != nil { - log.Error(err) + if err := pe.Get(ctx, c); err != nil { + runIfNotDone(ctx, func() { + log.Errorf( + "factom.PendingEntries.Get(): %v", + err) + }) return } @@ -311,7 +338,9 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Process all pending entries for this // chain. if err := ProcessPending(pe[i:j]...); err != nil { - log.Error(err) + runIfNotDone(ctx, func() { + log.Error(err) + }) return } } @@ -320,13 +349,13 @@ func engine(stop <-chan struct{}, done chan struct{}) { // Wait until the next scan tick or we're told to stop. select { case <-scanTicker.C: - case <-stop: + case <-ctx.Done(): return } } // Check the Factom blockchain height but log and retry if this request fails. - if err := updateFactomHeight(); err != nil { + if err := updateFactomHeight(ctx); err != nil { log.Error(err) if flag.FactomScanRetries > -1 && retries >= flag.FactomScanRetries { @@ -359,12 +388,12 @@ func setSyncHeight(sync uint32) { syncHeight = sync } -func updateFactomHeight() error { +func updateFactomHeight(ctx context.Context) error { // Get the current Factom Blockchain height. var heights factom.Heights - err := heights.Get(c) + err := heights.Get(ctx, c) if err != nil { - return fmt.Errorf("factom.Heights.Get(c): %v", err) + return fmt.Errorf("factom.Heights.Get(): %v", err) } heightMtx.Lock() defer heightMtx.Unlock() diff --git a/engine/process.go b/engine/process.go index 9ee75cc..71b012f 100644 --- a/engine/process.go +++ b/engine/process.go @@ -24,15 +24,16 @@ package engine import ( "bytes" + "context" "time" "crawshaw.io/sqlite" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" ) -func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { +func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) error { chain := Chains.Get(eb.ChainID) // Skip ignored chains and if we are ignoring new chain, also skip @@ -44,7 +45,7 @@ func Process(dbKeyMR *factom.Bytes32, eb factom.EBlock) error { if chain.IsUnknown() { // Attempt to open a new chain. var err error - chain, err = OpenNew(c, dbKeyMR, eb) + chain, err = OpenNew(ctx, c, dbKeyMR, eb) if err != nil { return err } @@ -177,10 +178,10 @@ func ProcessPending(es ...factom.Entry) error { // There is a chance the Identity is populated now but wasn't // before, so update it now. - if err := chain.Identity.Get(c); err != nil { - // A jrpc.Error indicates that the identity chain + if err := chain.Identity.Get(context.TODO(), c); err != nil { + // A jsonrpc2.Error indicates that the identity chain // doesn't yet exist, which we tolerate. - if _, ok := err.(jrpc.Error); !ok { + if _, ok := err.(jsonrpc2.Error); !ok { return err } } @@ -198,7 +199,7 @@ func ProcessPending(es ...factom.Entry) error { } // Load the Entry data. - if err := e.Get(c); err != nil { + if err := e.Get(context.TODO(), c); err != nil { return err } diff --git a/fat/entry.go b/fat/entry.go index 9543158..0a5d671 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -38,7 +38,7 @@ import ( ) type Transaction interface { - Validate(factom.IDKey) error + Validate(*factom.ID1Key) error IsCoinbase() bool FactomEntry() factom.Entry UnmarshalEntry() error @@ -185,6 +185,11 @@ func (e Entry) FAAddress(rcdSigID int) factom.FAAddress { return factom.FAAddress(sha256d(e.ExtIDs[id])) } +// ID1Key computes the ID1Key corresponding to the 1st RCD/Sig pair. +func (e Entry) ID1Key() factom.ID1Key { + return factom.ID1Key(e.FAAddress(0)) +} + // sha256d computes two rounds of the sha256 hash. func sha256d(data []byte) [sha256.Size]byte { hash := sha256.Sum256(data) diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index 474d7f5..7b73bdc 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -118,7 +118,7 @@ func (t Transaction) IsCoinbase() bool { // Validate performs all validation checks and returns nil if t is a valid // Transaction. If t is a coinbase transaction then idKey is used to validate // the RCD. Otherwise RCDs are checked against the input addresses. -func (t *Transaction) Validate(idKey factom.IDKey) error { +func (t *Transaction) Validate(idKey *factom.ID1Key) error { if err := t.UnmarshalEntry(); err != nil { return err } @@ -126,7 +126,7 @@ func (t *Transaction) Validate(idKey factom.IDKey) error { return err } if t.IsCoinbase() { - if t.FAAddress(0) != idKey.RCDHash() { + if t.ID1Key() != *idKey { return fmt.Errorf("invalid RCD") } } else { diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index 26f90e8..542290c 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -153,7 +153,7 @@ func (t *Transaction) MarshalEntry() error { return t.Entry.MarshalEntry(t) } -func (t *Transaction) Validate(idKey factom.IDKey) error { +func (t *Transaction) Validate(idKey *factom.ID1Key) error { if err := t.UnmarshalEntry(); err != nil { return err } @@ -161,7 +161,7 @@ func (t *Transaction) Validate(idKey factom.IDKey) error { return err } if t.IsCoinbase() { - if t.FAAddress(0) != idKey.RCDHash() { + if t.ID1Key() != *idKey { return fmt.Errorf("invalid RCD") } } else { diff --git a/fat/issuance.go b/fat/issuance.go index c555632..21d2a19 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -104,14 +104,14 @@ func (i *Issuance) MarshalEntry() error { // Validate performs all validation checks and returns nil if i is a valid // Issuance. -func (i *Issuance) Validate(idKey factom.IDKey) error { +func (i *Issuance) Validate(idKey *factom.ID1Key) error { if err := i.UnmarshalEntry(); err != nil { return err } if err := i.ValidExtIDs(); err != nil { return err } - if i.FAAddress(0) != idKey.Payload() { + if i.ID1Key() != *idKey { return fmt.Errorf("invalid RCD") } return nil diff --git a/flag/flag.go b/flag/flag.go index 7ba9ef3..66b4763 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -23,6 +23,7 @@ package flag import ( + "context" "flag" "fmt" "os" @@ -368,7 +369,7 @@ func Validate() { DBPath += fmt.Sprintf("%c", filepath.Separator) if factom.Bytes32(EsAdr).IsZero() { - EsAdr, _ = ECAdr.GetEsAddress(FactomClient) + EsAdr, _ = ECAdr.GetEsAddress(context.TODO(), FactomClient) } else { ECAdr = EsAdr.ECAddress() } diff --git a/flag/predict.go b/flag/predict.go index 6b3b9f5..f6b28c9 100644 --- a/flag/predict.go +++ b/flag/predict.go @@ -23,12 +23,12 @@ package flag import ( + "context" "flag" "os" "strings" "time" - "github.com/Factom-Asset-Tokens/factom" "github.com/posener/complete" ) @@ -87,29 +87,24 @@ func predictAddress(fa bool, num int, flagName, suffix string) complete.PredictF func listAddresses(fa bool) []string { parseWalletFlags() - var adrs []factom.Address + fss, ess, err := FactomClient.GetPrivateAddresses(context.Background()) + if err != nil { + os.Exit(6) + } + var adrStrs []string if fa { - as, _ := FactomClient.GetFAAddresses() - adrs = make([]factom.Address, len(as)) - for i, adr := range as { - adrs[i] = adr + adrStrs = make([]string, len(fss)) + for i, fs := range fss { + adrStrs[i] = fs.FAAddress().String() } } else { - as, _ := FactomClient.GetECAddresses() - adrs = make([]factom.Address, len(as)) - for i, adr := range as { - adrs[i] = adr + adrStrs = make([]string, len(ess)) + for i, es := range ess { + adrStrs[i] = es.ECAddress().String() } } - adrStrs := make([]string, len(adrs)) - for i, adr := range adrs { - adrStrs[i] = adr.String() - } return adrStrs } -func String(adr factom.Address) string { - return adr.String() -} var cliFlags *flag.FlagSet @@ -126,16 +121,16 @@ func parseWalletFlags() { cliFlags.StringVar(&FactomClient.WalletdServer, "w", "localhost:8089", "") cliFlags.StringVar(&FactomClient.Walletd.User, "walletuser", "", "") cliFlags.StringVar(&FactomClient.Walletd.Password, "walletpassword", "", "") - //cliFlags.StringVar(&FactomClient.Walletd.TLSCertFile, "walletcert", "~/.factom/walletAPIpub.cert", "") - //cliFlags.BoolVar(&factom.RpcConfig.WalletTLSEnable, "wallettls", false, "") // flags.Parse will print warnings if it comes across an unrecognized // flag. We don't want this so we temprorarily redirect everything to // /dev/null before we call flags.Parse(). - stdout := os.Stdout - stderr := os.Stderr - os.Stdout, _ = os.Open(os.DevNull) - os.Stderr = os.Stdout + stdout, stderr := os.Stdout, os.Stderr + devNull, err := os.Open(os.DevNull) + if err != nil { + os.Exit(5) + } + os.Stdout, os.Stderr = devNull, devNull // The current command line being typed is stored in the environment // variable COMP_LINE. We split on spaces and discard the first in the @@ -143,11 +138,8 @@ func parseWalletFlags() { cliFlags.Parse(strings.Fields(os.Getenv("COMP_LINE"))[1:]) // Restore stdout and stderr. - os.Stdout = stdout - os.Stderr = stderr + os.Stdout, os.Stderr = stdout, stderr - // We want need factom-walletd to timeout or the CLI completion will - // hang and never return. This is the whole reason we use AdamSLevy's - // fork of factom. - FactomClient.Walletd.Timeout = 1 * time.Second + // We need a short timeout or the CLI completion will hang. + FactomClient.Walletd.Timeout = time.Second / 2 } diff --git a/go.mod b/go.mod index 0bce0b2..3cfbfde 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,9 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb - github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 - github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409 + github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d - github.com/Factom-Asset-Tokens/factom v0.0.0-20191002021226-1bb51d430207 + github.com/Factom-Asset-Tokens/factom v0.0.0-20191006004400-d347b69e7a7b github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index e118633..64c2ba1 100644 --- a/go.sum +++ b/go.sum @@ -3,10 +3,8 @@ crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5Z crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBnL4rLgeNaspUYnmnvkXcmDA3QO3rHBGgU= github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= -github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 h1:McSW/pP7K0/Ucjig6AJwW7Khph/XOMYhSB8v3YxMBl4= -github.com/AdamSLevy/jsonrpc2/v11 v11.3.2/go.mod h1:7fNjH6BXM0KVswWqj+K/mnOS8wiSke0sE8X46hS+nsc= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409 h1:s7+6wUZJvorkJWm6Eyifm4NDbmqGqJfFsZvtz416L2A= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20190929214639-94306464b409/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmKY1f0AITEyfhLaH9zpqIwLgVIKLE+BBCY2B2gys= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 h1:eNbDxd8OsXjADVKdoDZEQwLqA08rzCUxvgnOiZAf6hs= @@ -17,8 +15,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191002021226-1bb51d430207 h1:YWqkcnkOGv/AoW+G/qJpOPAhi9i6udc2e4UJMGXweAM= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191002021226-1bb51d430207/go.mod h1:XbZ9AweH09WKgxrfPIEauJ5/2XL/zroNmDAbd3TdKXk= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191006004400-d347b69e7a7b h1:6ux+qEf0rxQVBfETVgGimR+QojbcGnXDUgCe+B/MKHM= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191006004400-d347b69e7a7b/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -138,8 +136,6 @@ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 h1:Ko2LQMrRU+Oy/+EDBwX7eZ2jp3C47eDBB8EIhKTun+I= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= diff --git a/main.go b/main.go index d93198d..2e27035 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ package main import ( + "context" "os" "os/signal" @@ -45,44 +46,47 @@ func _main() (ret int) { // Set up interrupts channel. We don't want to be interrupted during // initialization. If the signal is sent we will handle it later. + ctx, cancel := context.WithCancel(context.Background()) sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) + go func() { + <-sigint + cancel() + }() log := log.New("pkg", "main") log.Info("Fatd Version: ", flag.Revision) defer log.Info("Factom Asset Token Daemon stopped.") // Engine - stopEngine := make(chan struct{}) - engineDone := engine.Start(stopEngine) + engineDone := engine.Start(ctx) if engineDone == nil { return 1 } defer func() { - close(stopEngine) // Stop engine. - <-engineDone // Wait for engine to stop. + <-engineDone // Wait for engine to stop. log.Info("State engine stopped.") }() log.Info("State engine started.") // Server - stopSrv := make(chan struct{}) - srvDone := srv.Start(stopSrv) + srvDone := srv.Start(ctx) if srvDone == nil { return 1 } defer func() { - close(stopSrv) // Stop server. - <-srvDone // Wait for server to stop. + <-srvDone // Wait for server to stop. log.Info("JSON RPC API server stopped.") }() log.Info("JSON RPC API server started.") log.Info("Factom Asset Token Daemon started.") - defer signal.Reset() // Stop handling signals once we return. + // Stop handling signals once we return. + defer func() { signal.Reset(); close(sigint) }() + select { - case <-sigint: + case <-ctx.Done(): log.Infof("SIGINT: Shutting down...") return 0 case <-engineDone: // Closed if engine exits prematurely. diff --git a/srv/client.go b/srv/client.go index 2808eab..f9c02da 100644 --- a/srv/client.go +++ b/srv/client.go @@ -23,6 +23,7 @@ package srv import ( + "context" "fmt" "time" @@ -52,10 +53,11 @@ func NewClient() *Client { } // Request makes a request to fatd's v1 API. -func (c *Client) Request(method string, params, result interface{}) error { - url := c.FatdServer + "/v1" +func (c *Client) Request(ctx context.Context, + method string, params, result interface{}) error { + if c.DebugRequest { - fmt.Println("fatd:", url) + fmt.Println("fatd:", c.FatdServer) } - return c.Client.Request(url, method, params, result) + return c.Client.Request(ctx, c.FatdServer, method, params, result) } diff --git a/srv/errors.go b/srv/errors.go index 6775d65..2bff1bb 100644 --- a/srv/errors.go +++ b/srv/errors.go @@ -22,18 +22,18 @@ package srv -import jrpc "github.com/AdamSLevy/jsonrpc2/v11" +import jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" var ( - ErrorTokenNotFound = jrpc.NewError(-32800, "Token Not Found", + ErrorTokenNotFound = jsonrpc2.NewError(-32800, "Token Not Found", "token may be invalid, or not yet issued or tracked") - ErrorTransactionNotFound = jrpc.NewError(-32803, "Transaction Not Found", + ErrorTransactionNotFound = jsonrpc2.NewError(-32803, "Transaction Not Found", "no matching tx-id was found") - ErrorInvalidTransaction = jrpc.NewError(-32804, "Invalid Transaction", nil) - ErrorTokenSyncing = jrpc.NewError(-32805, "Token Syncing", + ErrorInvalidTransaction = jsonrpc2.NewError(-32804, "Invalid Transaction", nil) + ErrorTokenSyncing = jsonrpc2.NewError(-32805, "Token Syncing", "token is in the process of syncing") - ErrorNoEC = jrpc.NewError(-32806, "No Entry Credits", + ErrorNoEC = jsonrpc2.NewError(-32806, "No Entry Credits", "not configured with entry credits") - ErrorPendingDisabled = jrpc.NewError(-32807, "Pending Transactions Disabled", + ErrorPendingDisabled = jsonrpc2.NewError(-32807, "Pending Transactions Disabled", "fatd is not tracking pending transactions") ) diff --git a/srv/methods.go b/srv/methods.go index d381b35..abad2b5 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -24,10 +24,12 @@ package srv import ( "bytes" + "context" "encoding/json" "fmt" + "time" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/db/addresses" @@ -42,7 +44,7 @@ import ( var c = flag.FactomClient -var jrpcMethods = jrpc.MethodMap{ +var jsonrpc2Methods = jsonrpc2.MethodMap{ "get-issuance": getIssuance(false), "get-issuance-entry": getIssuance(true), "get-transaction": getTransaction(false), @@ -70,10 +72,10 @@ type ResultGetIssuance struct { Issuance fat.Issuance `json:"issuance"` } -func getIssuance(entry bool) jrpc.MethodFunc { - return func(data json.RawMessage) interface{} { +func getIssuance(entry bool) jsonrpc2.MethodFunc { + return func(ctx context.Context, data json.RawMessage) interface{} { params := ParamsToken{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -102,10 +104,10 @@ type ResultGetTransaction struct { Pending bool `json:"pending,omitempty"` } -func getTransaction(getEntry bool) jrpc.MethodFunc { - return func(data json.RawMessage) interface{} { +func getTransaction(getEntry bool) jsonrpc2.MethodFunc { + return func(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetTransaction{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -146,10 +148,10 @@ func getTransaction(getEntry bool) jrpc.MethodFunc { } } -func getTransactions(getEntry bool) jrpc.MethodFunc { - return func(data json.RawMessage) interface{} { +func getTransactions(getEntry bool) jsonrpc2.MethodFunc { + return func(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetTransactions{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -210,9 +212,9 @@ func getTransactions(getEntry bool) jrpc.MethodFunc { } } -func getBalance(data json.RawMessage) interface{} { +func getBalance(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetBalance{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -234,6 +236,7 @@ func (r ResultGetBalances) MarshalJSON() ([]byte, error) { } return json.Marshal(strMap) } + func (r *ResultGetBalances) UnmarshalJSON(data []byte) error { var strMap map[string]uint64 if err := json.Unmarshal(data, &strMap); err != nil { @@ -250,9 +253,9 @@ func (r *ResultGetBalances) UnmarshalJSON(data []byte) error { return nil } -func getBalances(data json.RawMessage) interface{} { +func getBalances(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetBalances{} - if _, _, err := validate(data, ¶ms); err != nil { + if _, _, err := validate(ctx, data, ¶ms); err != nil { return err } @@ -273,9 +276,9 @@ func getBalances(data json.RawMessage) interface{} { return balances } -func getNFBalance(data json.RawMessage) interface{} { +func getNFBalance(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetNFBalance{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -316,9 +319,9 @@ type ResultGetStats struct { var coinbaseRCDHash = fat.Coinbase() -func getStats(data json.RawMessage) interface{} { +func getStats(ctx context.Context, data json.RawMessage) interface{} { params := ParamsToken{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -365,9 +368,9 @@ type ResultGetNFToken struct { CreationTx *factom.Bytes32 `json:"creationtx"` } -func getNFToken(data json.RawMessage) interface{} { +func getNFToken(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetNFToken{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -404,9 +407,9 @@ func getNFToken(data json.RawMessage) interface{} { return res } -func getNFTokens(data json.RawMessage) interface{} { +func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { params := ParamsGetAllNFTokens{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -439,9 +442,9 @@ func getNFTokens(data json.RawMessage) interface{} { return res } -func sendTransaction(data json.RawMessage) interface{} { +func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { params := ParamsSendTransaction{} - chain, put, err := validate(data, ¶ms) + chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } @@ -469,7 +472,7 @@ func sendTransaction(data json.RawMessage) interface{} { var txID *factom.Bytes32 if !params.DryRun { - balance, err := flag.ECAdr.GetBalance(c) + balance, err := flag.ECAdr.GetBalance(ctx, c) if err != nil { panic(err) } @@ -479,12 +482,12 @@ func sendTransaction(data json.RawMessage) interface{} { } txID = new(factom.Bytes32) var commit []byte - commit, *txID = factom.GenerateCommit(flag.EsAdr, - params.Raw, entry.Hash, false) - if err := c.Commit(commit); err != nil { + commit, *txID = factom.GenerateCommit( + flag.EsAdr, params.Raw, entry.Hash, false) + if err := c.Commit(ctx, commit); err != nil { panic(err) } - if err := c.Reveal(params.Raw); err != nil { + if err := c.Reveal(ctx, params.Raw); err != nil { panic(err) } @@ -596,8 +599,8 @@ func applyTx(chain *engine.Chain, tx fat.Transaction) (txErr, err error) { return } -func getDaemonTokens(data json.RawMessage) interface{} { - if _, _, err := validate(data, nil); err != nil { +func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { + if _, _, err := validate(ctx, data, nil); err != nil { return err } @@ -619,8 +622,8 @@ type ResultGetDaemonProperties struct { NetworkID factom.NetworkID `json:"factomnetworkid"` } -func getDaemonProperties(data json.RawMessage) interface{} { - if _, _, err := validate(data, nil); err != nil { +func getDaemonProperties(ctx context.Context, data json.RawMessage) interface{} { + if _, _, err := validate(ctx, data, nil); err != nil { return err } return ResultGetDaemonProperties{ @@ -635,15 +638,17 @@ type ResultGetSyncStatus struct { Current uint32 `json:"factomheight"` } -func getSyncStatus(data json.RawMessage) interface{} { +func getSyncStatus(ctx context.Context, data json.RawMessage) interface{} { sync, current := engine.GetSyncStatus() return ResultGetSyncStatus{Sync: sync, Current: current} } -func validate(data json.RawMessage, params Params) (*engine.Chain, func(), error) { +func validate(ctx context.Context, + data json.RawMessage, params Params) (*engine.Chain, func(), error) { if params == nil { if len(data) > 0 { - return nil, nil, jrpc.InvalidParams(`no "params" accepted`) + return nil, nil, jsonrpc2.ErrorInvalidParams( + `no "params" accepted`) } return nil, nil, nil } @@ -651,7 +656,7 @@ func validate(data json.RawMessage, params Params) (*engine.Chain, func(), error return nil, nil, params.IsValid() } if err := unmarshalStrict(data, params); err != nil { - return nil, nil, jrpc.InvalidParams(err.Error()) + return nil, nil, jsonrpc2.ErrorInvalidParams(err) } if err := params.IsValid(); err != nil { return nil, nil, err @@ -666,7 +671,9 @@ func validate(data json.RawMessage, params Params) (*engine.Chain, func(), error return nil, nil, ErrorTokenNotFound } put := chain.Get(params.HasIncludePending()) - return &chain, put, nil + ctx, cancel := context.WithTimeout(ctx, time.Second) + chain.Conn.SetInterrupt(ctx.Done()) + return &chain, func() { cancel(); put() }, nil } return nil, nil, nil } diff --git a/srv/params.go b/srv/params.go index 7c03b04..256cacb 100644 --- a/srv/params.go +++ b/srv/params.go @@ -26,7 +26,7 @@ import ( "strings" "time" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" @@ -51,23 +51,23 @@ type ParamsToken struct { func (p ParamsToken) IsValid() error { if p.ChainID != nil { if len(p.TokenID) > 0 || p.IssuerChainID != nil { - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `cannot use "chainid" with "tokenid" or "issuerid"`) } return nil } if len(p.TokenID) > 0 || p.IssuerChainID != nil { if len(p.TokenID) == 0 { - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"tokenid" is required with "issuerid"`) } if p.IssuerChainID == nil { - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"issuerid" is required with "tokenid"`) } return nil } - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `required: either "chainid" or both "tokenid" and "issuerid"`) } @@ -93,7 +93,7 @@ func (p *ParamsPagination) IsValid() error { p.Page = new(uint) *p.Page = 1 } else if *p.Page == 0 { - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"order" value must be either "asc" or "desc"`) } if p.Limit == 0 { @@ -105,7 +105,7 @@ func (p *ParamsPagination) IsValid() error { case "", "asc", "desc": // ok default: - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"order" value must be either "asc" or "desc"`) } @@ -124,7 +124,7 @@ func (p ParamsGetTransaction) IsValid() error { return err } if p.Hash == nil { - return jrpc.InvalidParams(`required: "entryhash"`) + return jsonrpc2.ErrorInvalidParams(`required: "entryhash"`) } return nil } @@ -151,13 +151,13 @@ func (p *ParamsGetTransactions) IsValid() error { switch p.ToFrom { case "to", "from": if len(p.Addresses) == 0 { - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"addresses" may not be empty when "tofrom" is set`) } case "": // empty is ok default: - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"tofrom" value must be either "to" or "from"`) } return nil @@ -173,7 +173,7 @@ func (p ParamsGetNFToken) IsValid() error { return err } if p.NFTokenID == nil { - return jrpc.InvalidParams(`required: "nftokenid"`) + return jsonrpc2.ErrorInvalidParams(`required: "nftokenid"`) } return nil } @@ -188,7 +188,7 @@ func (p ParamsGetBalance) IsValid() error { return err } if p.Address == nil { - return jrpc.InvalidParams(`required: "address"`) + return jsonrpc2.ErrorInvalidParams(`required: "address"`) } return nil } @@ -202,7 +202,7 @@ func (p ParamsGetBalances) HasIncludePending() bool { return p.IncludePending } func (p ParamsGetBalances) IsValid() error { if p.Address == nil { - return jrpc.InvalidParams(`required: "address"`) + return jsonrpc2.ErrorInvalidParams(`required: "address"`) } return nil } @@ -224,7 +224,7 @@ func (p *ParamsGetNFBalance) IsValid() error { return err } if p.Address == nil { - return jrpc.InvalidParams(`required: "address"`) + return jsonrpc2.ErrorInvalidParams(`required: "address"`) } return nil } @@ -257,11 +257,11 @@ func (p *ParamsSendTransaction) IsValid() error { if p.Raw != nil { if p.ExtIDs != nil || p.Content != nil || p.ParamsToken != (ParamsToken{}) { - return jrpc.InvalidParams( + return jsonrpc2.ErrorInvalidParams( `"raw cannot be used with "content" or "extids"`) } if err := p.entry.UnmarshalBinary(p.Raw); err != nil { - return jrpc.InvalidParams(err.Error()) + return jsonrpc2.ErrorInvalidParams(err) } p.entry.Timestamp = time.Now() p.ChainID = p.entry.ChainID @@ -271,7 +271,8 @@ func (p *ParamsSendTransaction) IsValid() error { return err } if len(p.Content) == 0 || len(p.ExtIDs) == 0 { - return jrpc.InvalidParams(`required: "raw" or "content" and "extids"`) + return jsonrpc2.ErrorInvalidParams( + `required: "raw" or "content" and "extids"`) } p.entry = factom.Entry{ ExtIDs: p.ExtIDs, @@ -282,7 +283,7 @@ func (p *ParamsSendTransaction) IsValid() error { data, err := p.entry.MarshalBinary() if err != nil { - return jrpc.InvalidParams(err) + return jsonrpc2.ErrorInvalidParams(err) } hash := factom.ComputeEntryHash(data) p.entry.Hash = &hash diff --git a/srv/srv.go b/srv/srv.go index 430892d..456ecd0 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -23,9 +23,11 @@ package srv import ( + "context" "net/http" + "time" - jrpc "github.com/AdamSLevy/jsonrpc2/v11" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/fatd/flag" _log "github.com/Factom-Asset-Tokens/fatd/log" "github.com/goji/httpauth" @@ -45,12 +47,12 @@ var ( // closed and any goroutines will exit. The done channel is closed when the // server exits for any reason. If the done channel is closed before the stop // channel is closed, an error occurred. Errors are logged. -func Start(stop <-chan struct{}) (done <-chan struct{}) { +func Start(ctx context.Context) (done <-chan struct{}) { log = _log.New("pkg", "srv") // Set up JSON RPC 2.0 handler with correct headers. - jrpc.DebugMethodFunc = true - jrpcHandler := jrpc.HTTPRequestHandler(jrpcMethods) + jsonrpc2.DebugMethodFunc = true + jrpcHandler := jsonrpc2.HTTPRequestHandler(jsonrpc2Methods) var handler http.Handler = http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { @@ -101,8 +103,11 @@ func Start(stop <-chan struct{}) (done <-chan struct{}) { // Listen for stop signal. go func() { select { - case <-stop: - if err := srv.Shutdown(nil); err != nil { + case <-ctx.Done(): + ctx, cancel := context.WithTimeout( + context.Background(), 5*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { log.Errorf("srv.Shutdown(): %v", err) } case <-_done: From 2ae8befe89faedf4d2aa6fbae71a1f8be1377381 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 6 Oct 2019 16:03:19 -0800 Subject: [PATCH 071/124] build(go.mod): Update sqlite to v3.30.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3cfbfde..e1c79d3 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,6 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191006235146-265abd16c79e //replace github.com/Factom-Asset-Tokens/factom => ../factom diff --git a/go.sum b/go.sum index 64c2ba1..2836518 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmK github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9 h1:eNbDxd8OsXjADVKdoDZEQwLqA08rzCUxvgnOiZAf6hs= -github.com/AdamSLevy/sqlite v0.1.3-0.20190925021041-5f14912819e9/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlite v0.1.3-0.20191006235146-265abd16c79e h1:kAnayRbaLuU3x8yUnWhZ9mLievlarBszRm4jsB4UyFk= +github.com/AdamSLevy/sqlite v0.1.3-0.20191006235146-265abd16c79e/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d h1:yPm4An70OhM4k4WUq7M9sWaVlFas2+hJB+I3Fsgw38A= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= From c55a36c37f8bfacbd9b818c579af93d946cdeca3 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sun, 6 Oct 2019 19:06:37 -0800 Subject: [PATCH 072/124] build(go.mod): Update sqlite to avoid panic --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e1c79d3..40dc251 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,6 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191006235146-265abd16c79e +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191007030329-a2a45e244691 //replace github.com/Factom-Asset-Tokens/factom => ../factom diff --git a/go.sum b/go.sum index 2836518..d081967 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmK github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20191006235146-265abd16c79e h1:kAnayRbaLuU3x8yUnWhZ9mLievlarBszRm4jsB4UyFk= -github.com/AdamSLevy/sqlite v0.1.3-0.20191006235146-265abd16c79e/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlite v0.1.3-0.20191007030329-a2a45e244691 h1:b9infNdfhwgJJ1pMinwdyfq8tS74094DcT4v/iHk36U= +github.com/AdamSLevy/sqlite v0.1.3-0.20191007030329-a2a45e244691/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d h1:yPm4An70OhM4k4WUq7M9sWaVlFas2+hJB+I3Fsgw38A= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= From 2c33e9ad1679e6b20a56c6ba1dc46309c50bc66b Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 7 Oct 2019 15:28:32 -0800 Subject: [PATCH 073/124] fix(db): Synchronize on db.Chain.Close to ensure WAL file cleanup Also use proper error wrapping when returning fmt.Errorf --- cli/cmd/transactfat0.go | 4 +-- cli/cmd/transactfat1.go | 6 ++--- db/chain.go | 39 ++++++++++++++------------- db/eblocks/eblocks.go | 7 ++--- db/entries/entries.go | 6 ++--- db/metadata/metadata.go | 2 +- db/schema.go | 2 +- engine/chain.go | 48 +++++++++++++++++----------------- engine/chainmap.go | 3 ++- engine/engine.go | 8 +++--- engine/process.go | 10 +++---- fat/entry.go | 2 +- fat/fat0/addressamountmap.go | 2 +- fat/fat0/transaction.go | 8 +++--- fat/fat1/addressnftokensmap.go | 12 ++++----- fat/fat1/nftokenidrange.go | 4 +-- fat/fat1/nftokenmetadata.go | 4 +-- fat/fat1/nftokens.go | 8 +++--- fat/fat1/transaction.go | 16 ++++++------ fat/issuance.go | 4 +-- fat/type.go | 2 +- main.go | 13 ++++++--- srv/srv.go | 4 +-- 23 files changed, 112 insertions(+), 102 deletions(-) diff --git a/cli/cmd/transactfat0.go b/cli/cmd/transactfat0.go index ac3d188..e24010c 100644 --- a/cli/cmd/transactfat0.go +++ b/cli/cmd/transactfat0.go @@ -129,7 +129,7 @@ func (m AddressAmountMap) set(data string) error { if err := fa.Set(adrStr); err != nil { // Not FA, try FsAddress... if err := fs.Set(adrStr); err != nil { - return fmt.Errorf("invalid address: %v", err) + return fmt.Errorf("invalid address: %w", err) } fa = fs.FAAddress() if fa != fat.Coinbase() { @@ -145,7 +145,7 @@ func (m AddressAmountMap) set(data string) error { // Parse amount amount, err := parsePositiveInt(amountStr) if err != nil { - return fmt.Errorf("invalid amount: %v", err) + return fmt.Errorf("invalid amount: %w", err) } m[fa] = amount addressValueStrMap[fa] = amountStr diff --git a/cli/cmd/transactfat1.go b/cli/cmd/transactfat1.go index 09ee4c5..a510a77 100644 --- a/cli/cmd/transactfat1.go +++ b/cli/cmd/transactfat1.go @@ -129,7 +129,7 @@ func (m AddressNFTokensMap) set(data string) error { if err := fa.Set(adrStr); err != nil { // Not FA, try FsAddress... if err := fs.Set(adrStr); err != nil { - return fmt.Errorf("invalid address: %v", err) + return fmt.Errorf("invalid address: %w", err) } fa = fs.FAAddress() if fa != fat.Coinbase() { @@ -209,7 +209,7 @@ func (tkns NFTokens) set(data string) error { } // Set all NFTokenIDs to the NFTokens map. if err := fat1.NFTokens(tkns).Set(tknIDs); err != nil { - return fmt.Errorf("invalid NFTokens: %v", err) + return fmt.Errorf("invalid NFTokens: %w", err) } } return nil @@ -221,7 +221,7 @@ func (tkns NFTokens) String() string { func parseNFTokenID(tknIDStr string) (fat1.NFTokenID, error) { tknID, err := parsePositiveInt(tknIDStr) if err != nil { - return 0, fmt.Errorf("invalid NFTokenID: %v", err) + return 0, fmt.Errorf("invalid NFTokenID: %w", err) } return fat1.NFTokenID(tknID), nil } diff --git a/db/chain.go b/db/chain.go index b18bf87..1b428ee 100644 --- a/db/chain.go +++ b/db/chain.go @@ -29,7 +29,6 @@ import ( "os" "strings" "time" - //"strings" "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" @@ -56,17 +55,18 @@ const ( ) type Chain struct { - ID *factom.Bytes32 - TokenID string - IssuerChainID *factom.Bytes32 - Head factom.EBlock - DBKeyMR *factom.Bytes32 - factom.Identity - NetworkID factom.NetworkID - + // General Factom Blockchain Data + ID *factom.Bytes32 + Head factom.EBlock + HeadDBKeyMR *factom.Bytes32 + NetworkID factom.NetworkID SyncHeight uint32 SyncDBKeyMR *factom.Bytes32 + // FAT Specific Data + TokenID string + IssuerChainID *factom.Bytes32 + factom.Identity fat.Issuance NumIssued uint64 @@ -94,7 +94,7 @@ func OpenNew(ctx context.Context, dbPath string, // Ensure that the database file doesn't already exist. _, err = os.Stat(path) if err == nil { - err = fmt.Errorf("already exists: %v", path) + err = fmt.Errorf("already exists: %w", path) return } if !os.IsNotExist(err) { // Any other error is unexpected. @@ -109,7 +109,7 @@ func OpenNew(ctx context.Context, dbPath string, if err != nil { chain.Close() if err := os.Remove(path); err != nil { - chain.Log.Errorf("os.Remove(): %v", err) + chain.Log.Errorf("os.Remove(): %w", err) } } }() @@ -118,7 +118,7 @@ func OpenNew(ctx context.Context, dbPath string, chain.ID = eb.ChainID chain.IssuerChainID = new(factom.Bytes32) chain.TokenID, *chain.IssuerChainID = fat.TokenIssuer(nameIDs) - chain.DBKeyMR = dbKeyMR + chain.HeadDBKeyMR = dbKeyMR chain.Identity = identity chain.SyncHeight = eb.Height chain.SyncDBKeyMR = dbKeyMR @@ -175,7 +175,7 @@ func OpenAll(ctx context.Context, dbPath string) (chains []Chain, err error) { // file names. files, err := ioutil.ReadDir(dbPath) if err != nil { - return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", dbPath, err) + return nil, fmt.Errorf("ioutil.ReadDir(%q): %w", dbPath, err) } chains = make([]Chain, 0, len(files)) for _, f := range files { @@ -220,7 +220,7 @@ func OpenConnPool(ctx context.Context, dbURI string) ( sqlite.SQLITE_OPEN_NOMUTEX flags := baseFlags | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE if conn, err = sqlite.OpenConn(dbURI, flags); err != nil { - err = fmt.Errorf("sqlite.OpenConn(%q, %x): %v", dbURI, flags, err) + err = fmt.Errorf("sqlite.OpenConn(%q, %x): %w", dbURI, flags, err) return } defer func() { @@ -240,7 +240,7 @@ func OpenConnPool(ctx context.Context, dbURI string) ( flags = baseFlags | sqlite.SQLITE_OPEN_READONLY if pool, err = sqlitex.Open(dbURI, flags, PoolSize); err != nil { - err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %v", + err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %w", dbURI, flags, PoolSize, err) return } @@ -249,12 +249,15 @@ func OpenConnPool(ctx context.Context, dbURI string) ( // Close all database connections. Log any errors. func (chain *Chain) Close() { + chain.Log.Debug("Closing...") + chain.Conn.SetInterrupt(nil) + sqlitex.ExecScript(chain.Conn, `PRAGMA database.wal_checkpoint;`) if err := chain.Pool.Close(); err != nil { - chain.Log.Errorf("chain.Pool.Close(): %v", err) + chain.Log.Errorf("chain.Pool.Close(): %w", err) } // Close this last so that the wal and shm files are removed. if err := chain.Conn.Close(); err != nil { - chain.Log.Errorf("chain.Conn.Close(): %v", err) + chain.Log.Errorf("chain.Conn.Close(): %w", err) } } @@ -312,7 +315,7 @@ func (chain *Chain) loadMetadata() error { return fmt.Errorf("no eblock in database") } chain.Head = eb - chain.DBKeyMR = &dbKeyMR + chain.HeadDBKeyMR = &dbKeyMR chain.ID = eb.ChainID chain.SyncHeight, chain.NumIssued, chain.SyncDBKeyMR, diff --git a/db/eblocks/eblocks.go b/db/eblocks/eblocks.go index bd6c6d0..a240d44 100644 --- a/db/eblocks/eblocks.go +++ b/db/eblocks/eblocks.go @@ -48,13 +48,13 @@ func Insert(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) error // Ensure that this is the next EBlock. prevKeyMR, err := SelectKeyMR(conn, eb.Sequence-1) if *eb.PrevKeyMR != prevKeyMR { - return fmt.Errorf("invalid EBlock{}.PrevKeyMR") + return fmt.Errorf("invalid EBlock.PrevKeyMR") } var data []byte data, err = eb.MarshalBinary() if err != nil { - panic(fmt.Errorf("factom.EBlock{}.MarshalBinary(): %v", err)) + panic(fmt.Errorf("factom.EBlock.MarshalBinary(): %w", err)) } stmt := conn.Prep(`INSERT INTO "eblocks" ("seq", "key_mr", "db_height", "db_key_mr", "timestamp", "data") @@ -98,7 +98,8 @@ func Select(stmt *sqlite.Stmt) (factom.EBlock, error) { data := make([]byte, stmt.ColumnLen(1)) stmt.ColumnBytes(1, data) if err := eb.UnmarshalBinary(data); err != nil { - panic(fmt.Errorf("factom.EBlock{}.UnmarshalBinary(%x): %v", data, err)) + panic(fmt.Errorf("factom.EBlock.UnmarshalBinary(%v): %w", + factom.Bytes(data), err)) } return eb, nil diff --git a/db/entries/entries.go b/db/entries/entries.go index 3d0230c..d7df4a4 100644 --- a/db/entries/entries.go +++ b/db/entries/entries.go @@ -59,7 +59,7 @@ CREATE INDEX "idx_entries_hash" ON "entries"("hash"); func Insert(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) { data, err := e.MarshalBinary() if err != nil { - panic(fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err)) + panic(fmt.Errorf("factom.Entry.MarshalBinary(): %w", err)) } stmt := conn.Prep(`INSERT INTO "entries" @@ -115,8 +115,8 @@ func Select(stmt *sqlite.Stmt) (factom.Entry, error) { data := make([]byte, stmt.ColumnLen(1)) stmt.ColumnBytes(1, data) if err := e.UnmarshalBinary(data); err != nil { - panic(fmt.Errorf("factom.Entry{}.UnmarshalBinary(%x): %v", - data, err)) + panic(fmt.Errorf("factom.Entry.UnmarshalBinary(%v): %w", + factom.Bytes(data), err)) } e.Timestamp = time.Unix(stmt.ColumnInt64(2), 0) diff --git a/db/metadata/metadata.go b/db/metadata/metadata.go index ca3835b..b39cc64 100644 --- a/db/metadata/metadata.go +++ b/db/metadata/metadata.go @@ -157,7 +157,7 @@ func Select(conn *sqlite.Conn) (syncHeight uint32, numIssued uint64, idKeyEntryData := make(factom.Bytes, stmt.ColumnLen(3)) stmt.ColumnBytes(3, idKeyEntryData) if err = identity.UnmarshalBinary(idKeyEntryData); err != nil { - err = fmt.Errorf("identity.UnmarshalBinary(): %v", err) + err = fmt.Errorf("identity.UnmarshalBinary(): %w", err) return } identity.Height = uint32(stmt.ColumnInt64(4)) diff --git a/db/schema.go b/db/schema.go index 8ad8874..0552b70 100644 --- a/db/schema.go +++ b/db/schema.go @@ -56,7 +56,7 @@ func validateOrApplySchema(conn *sqlite.Conn, schema string) error { } if len(fullSchema) == 0 { if err := sqlitex.ExecScript(conn, schema); err != nil { - return fmt.Errorf("failed to apply schema: %v", err) + return fmt.Errorf("failed to apply schema: %w", err) } return nil } diff --git a/engine/chain.go b/engine/chain.go index b9e7c66..c77ce7c 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -59,13 +59,13 @@ func (chain Chain) String() string { func OpenNew(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) (chain Chain, err error) { - if err := eb.Get(context.TODO(), c); err != nil { - return chain, fmt.Errorf("%#v.Get(): %v", eb, err) + if err := eb.Get(ctx, c); err != nil { + return chain, fmt.Errorf("%#v.Get(): %w", eb, err) } // Load first entry of new chain. first := &eb.Entries[0] - if err := first.Get(context.TODO(), c); err != nil { - return chain, fmt.Errorf("%#v.Get(): %v", first, err) + if err := first.Get(ctx, c); err != nil { + return chain, fmt.Errorf("%#v.Get(): %w", first, err) } if !eb.IsFirst() { return @@ -80,7 +80,7 @@ func OpenNew(ctx context.Context, c *factom.Client, var identity factom.Identity identity.ChainID = new(factom.Bytes32) _, *identity.ChainID = fat.TokenIssuer(nameIDs) - if err = identity.Get(context.TODO(), c); err != nil { + if err = identity.Get(ctx, c); err != nil { // A jsonrpc2.Error indicates that the identity chain // doesn't yet exist, which we tolerate. if _, ok := err.(jsonrpc2.Error); !ok { @@ -88,14 +88,14 @@ func OpenNew(ctx context.Context, c *factom.Client, } } - if err := eb.GetEntries(context.TODO(), c); err != nil { - return chain, fmt.Errorf("%#v.GetEntries(): %v", eb, err) + if err := eb.GetEntries(ctx, c); err != nil { + return chain, fmt.Errorf("%#v.GetEntries(): %w", eb, err) } chain.Chain, err = db.OpenNew(ctx, flag.DBPath, dbKeyMR, eb, flag.NetworkID, identity) if err != nil { - return chain, fmt.Errorf("db.OpenNew(): %v", err) + return chain, fmt.Errorf("db.OpenNew(): %w", err) } if chain.Issuance.IsPopulated() { chain.ChainStatus = ChainStatusIssued @@ -107,17 +107,17 @@ func OpenNew(ctx context.Context, c *factom.Client, func (chain *Chain) OpenNewByChainID(ctx context.Context, c *factom.Client, chainID *factom.Bytes32) error { - eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(context.TODO(), c) + eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(ctx, c) if err != nil { - return fmt.Errorf("factom.EBlock{}.GetPrevAll(): %v", err) + return fmt.Errorf("factom.EBlock.GetPrevAll(): %w", err) } first := eblocks[len(eblocks)-1] // Get DBlock Timestamp and KeyMR var dblock factom.DBlock dblock.Height = first.Height - if err := dblock.Get(context.TODO(), c); err != nil { - return fmt.Errorf("factom.DBlock{}.Get(): %v", err) + if err := dblock.Get(ctx, c); err != nil { + return fmt.Errorf("factom.DBlock.Get(): %w", err) } first.SetTimestamp(dblock.Timestamp) @@ -130,41 +130,41 @@ func (chain *Chain) OpenNewByChainID(ctx context.Context, } // We already applied the first EBlock. Sync the remaining. - return chain.SyncEBlocks(c, eblocks[:len(eblocks)-1]) + return chain.SyncEBlocks(ctx, c, eblocks[:len(eblocks)-1]) } -func (chain *Chain) Sync(c *factom.Client) error { +func (chain *Chain) Sync(ctx context.Context, c *factom.Client) error { eblocks, err := factom.EBlock{ChainID: chain.ID}. - GetPrevUpTo(context.TODO(), c, *chain.Head.KeyMR) + GetPrevUpTo(ctx, c, *chain.Head.KeyMR) if err != nil { - return fmt.Errorf("factom.EBlock{}.GetPrevUpTo(): %v", err) + return fmt.Errorf("factom.EBlock.GetPrevUpTo(): %w", err) } - return chain.SyncEBlocks(c, eblocks) + return chain.SyncEBlocks(ctx, c, eblocks) } -func (chain *Chain) SyncEBlocks(c *factom.Client, ebs []factom.EBlock) error { +func (chain *Chain) SyncEBlocks(ctx context.Context, c *factom.Client, ebs []factom.EBlock) error { for i := range ebs { eb := ebs[len(ebs)-1-i] // Earliest EBlock first. // Get DBlock Timestamp and KeyMR var dblock factom.DBlock dblock.Height = eb.Height - if err := dblock.Get(context.TODO(), c); err != nil { - return fmt.Errorf("factom.DBlock{}.Get(): %v", err) + if err := dblock.Get(ctx, c); err != nil { + return fmt.Errorf("factom.DBlock.Get(): %w", err) } eb.SetTimestamp(dblock.Timestamp) - if err := chain.Apply(c, dblock.KeyMR, eb); err != nil { + if err := chain.Apply(ctx, c, dblock.KeyMR, eb); err != nil { return err } } return nil } -func (chain *Chain) Apply(c *factom.Client, +func (chain *Chain) Apply(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) error { // Get Identity each time in case it wasn't populated before. - if err := chain.Identity.Get(context.TODO(), c); err != nil { + if err := chain.Identity.Get(ctx, c); err != nil { // A jsonrpc2.Error indicates that the identity chain doesn't yet // exist, which we tolerate. if _, ok := err.(jsonrpc2.Error); !ok { @@ -172,7 +172,7 @@ func (chain *Chain) Apply(c *factom.Client, } } // Get all entry data. - if err := eb.GetEntries(context.TODO(), c); err != nil { + if err := eb.GetEntries(ctx, c); err != nil { return err } if err := chain.Chain.Apply(dbKeyMR, eb); err != nil { diff --git a/engine/chainmap.go b/engine/chainmap.go index e6938ee..d74c56e 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -107,6 +107,7 @@ func (cm ChainMap) setSync(height uint32, dbKeyMR *factom.Bytes32) error { func (cm ChainMap) Close() { defer cm.Unlock() cm.Lock() + log.Debug("Closing all chains...") for _, chain := range cm.m { if chain.IsTracked() { if chain.Pending.Session != nil { @@ -172,7 +173,7 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { return } - if err = chain.Sync(c); err != nil { + if err = chain.Sync(ctx, c); err != nil { dbChains = dbChains[i:] // Close remaining chains. return } diff --git a/engine/engine.go b/engine/engine.go index e9d2617..7c84c70 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -67,7 +67,7 @@ func Start(ctx context.Context) (done <-chan struct{}) { // they don't already exist. if err := os.Mkdir(flag.DBPath, 0755); err != nil { if !os.IsExist(err) { - log.Error(fmt.Errorf("os.Mkdir(%#v): %v", flag.DBPath, err)) + log.Errorf("os.Mkdir(%q): %v", flag.DBPath, err) return nil } } @@ -77,11 +77,11 @@ func Start(ctx context.Context) (done <-chan struct{}) { var err error lockFile, err = lockfile.New(lockFilePath) if err != nil { - log.Error(err) + log.Errorf("lockfile.New(%q): %v", lockFilePath, err) return } if err = lockFile.TryLock(); err != nil { - log.Errorf("Database in use by other process. %v", err) + log.Errorf("lockFile.TryLock(): %v", err) return } // Always clean up the lockfile if Start fails. @@ -337,7 +337,7 @@ func engine(ctx context.Context, done chan struct{}) { } // Process all pending entries for this // chain. - if err := ProcessPending(pe[i:j]...); err != nil { + if err := ProcessPending(ctx, pe[i:j]...); err != nil { runIfNotDone(ctx, func() { log.Error(err) }) diff --git a/engine/process.go b/engine/process.go index 71b012f..fd6cfb0 100644 --- a/engine/process.go +++ b/engine/process.go @@ -57,7 +57,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err } // Fully sync the chain, so that it is up to date immediately. - if err := chain.Sync(c); err != nil { + if err := chain.Sync(ctx, c); err != nil { return err } @@ -127,7 +127,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err prevStatus := chain.ChainStatus // Apply this EBlock to the chain. - if err := chain.Apply(c, dbKeyMR, eb); err != nil { + if err := chain.Apply(ctx, c, dbKeyMR, eb); err != nil { return err } @@ -141,7 +141,7 @@ func (chain Chain) conflictFn( return sqlite.SQLITE_CHANGESET_ABORT } -func ProcessPending(es ...factom.Entry) error { +func ProcessPending(ctx context.Context, es ...factom.Entry) error { chain := Chains.Get(es[0].ChainID) // We can only apply pending entries to tracked chains. @@ -178,7 +178,7 @@ func ProcessPending(es ...factom.Entry) error { // There is a chance the Identity is populated now but wasn't // before, so update it now. - if err := chain.Identity.Get(context.TODO(), c); err != nil { + if err := chain.Identity.Get(ctx, c); err != nil { // A jsonrpc2.Error indicates that the identity chain // doesn't yet exist, which we tolerate. if _, ok := err.(jsonrpc2.Error); !ok { @@ -199,7 +199,7 @@ func ProcessPending(es ...factom.Entry) error { } // Load the Entry data. - if err := e.Get(context.TODO(), c); err != nil { + if err := e.Get(ctx, c); err != nil { return err } diff --git a/fat/entry.go b/fat/entry.go index 0a5d671..5a74202 100644 --- a/fat/entry.go +++ b/fat/entry.go @@ -102,7 +102,7 @@ func (e Entry) ValidExtIDs(numRCDSigPairs int) error { func (e Entry) validTimestamp() error { sec, err := strconv.ParseInt(string(e.ExtIDs[0]), 10, 64) if err != nil { - return fmt.Errorf("timestamp salt: %v", err) + return fmt.Errorf("timestamp salt: %w", err) } ts := time.Unix(sec, 0) diff := e.Timestamp.Sub(ts) diff --git a/fat/fat0/addressamountmap.go b/fat/fat0/addressamountmap.go index 360c996..ef1a068 100644 --- a/fat/fat0/addressamountmap.go +++ b/fat/fat0/addressamountmap.go @@ -68,7 +68,7 @@ func (m *AddressAmountMap) UnmarshalJSON(data []byte) error { var adr factom.FAAddress for adrStr, amount := range adrStrAmountMap { if err := adr.Set(adrStr); err != nil { - return fmt.Errorf("%T: %v", m, err) + return fmt.Errorf("%T: %w", m, err) } if amount == 0 { return fmt.Errorf("%T: invalid amount (0): %v", m, adr) diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index 7b73bdc..8791414 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -57,18 +57,18 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { fat.Entry }{} if err := json.Unmarshal(data, &tRaw); err != nil { - return fmt.Errorf("%T: %v", t, err) + return fmt.Errorf("%T: %w", t, err) } if err := t.Inputs.UnmarshalJSON(tRaw.Inputs); err != nil { - return fmt.Errorf("%T.Inputs: %v", t, err) + return fmt.Errorf("%T.Inputs: %w", t, err) } if err := t.Outputs.UnmarshalJSON(tRaw.Outputs); err != nil { - return fmt.Errorf("%T.Outputs: %v", t, err) + return fmt.Errorf("%T.Outputs: %w", t, err) } t.Metadata = tRaw.Metadata if err := t.ValidData(); err != nil { - return fmt.Errorf("%T: %v", t, err) + return fmt.Errorf("%T: %w", t, err) } expectedJSONLen := len(`{"inputs":,"outputs":}`) + diff --git a/fat/fat1/addressnftokensmap.go b/fat/fat1/addressnftokensmap.go index 1c3f0f1..5f77ccd 100644 --- a/fat/fat1/addressnftokensmap.go +++ b/fat/fat1/addressnftokensmap.go @@ -54,7 +54,7 @@ func (m AddressNFTokensMap) MarshalJSON() ([]byte, error) { func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { var adrStrDataMap map[string]json.RawMessage if err := json.Unmarshal(data, &adrStrDataMap); err != nil { - return fmt.Errorf("%T: %v", m, err) + return fmt.Errorf("%T: %w", m, err) } if len(adrStrDataMap) == 0 { return fmt.Errorf("%T: empty", m) @@ -67,10 +67,10 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { var numTkns int for adrStr, data := range adrStrDataMap { if err := adr.Set(adrStr); err != nil { - return fmt.Errorf("%T: %#v: %v", m, adrStr, err) + return fmt.Errorf("%T: %#v: %w", m, adrStr, err) } if err := tkns.UnmarshalJSON(data); err != nil { - return fmt.Errorf("%T: %v: %v", m, err, adr) + return fmt.Errorf("%T: %v: %w", m, err, adr) } numTkns += len(tkns) if numTkns > maxCapacity { @@ -78,7 +78,7 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { m, numTkns-len(tkns), tkns, len(tkns), ErrorCapacity) } if err := m.NoNFTokensIntersection(tkns); err != nil { - return fmt.Errorf("%T: %v and %v", m, err, adr) + return fmt.Errorf("%T: %w and %v", m, err, adr) } (*m)[adr] = tkns expectedJSONLen += len(jsonlen.Compact(data)) @@ -92,7 +92,7 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { func (m AddressNFTokensMap) NoNFTokensIntersection(newTkns NFTokens) error { for adr, existingTkns := range m { if err := existingTkns.NoIntersection(newTkns); err != nil { - return fmt.Errorf("%v: %v", err, adr) + return fmt.Errorf("%w: %v", err, adr) } } return nil @@ -159,7 +159,7 @@ func (m AddressNFTokensMap) NoInternalNFTokensIntersection() error { delete(m, rcdHash) otherRCDHash := m.Owner(tknID) m[rcdHash] = tkns - return fmt.Errorf("%v: %v and %v", err, rcdHash, otherRCDHash) + return fmt.Errorf("%w: %v and %v", err, rcdHash, otherRCDHash) } } diff --git a/fat/fat1/nftokenidrange.go b/fat/fat1/nftokenidrange.go index 124c27e..6dec135 100644 --- a/fat/fat1/nftokenidrange.go +++ b/fat/fat1/nftokenidrange.go @@ -123,10 +123,10 @@ func (idRange NFTokenIDRange) Slice() []NFTokenID { func (idRange *NFTokenIDRange) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, (*nfTokenIDRange)(idRange)); err != nil { - return fmt.Errorf("%T: %v", idRange, err) + return fmt.Errorf("%T: %w", idRange, err) } if err := idRange.Valid(); err != nil { - return fmt.Errorf("%T: %v", idRange, err) + return fmt.Errorf("%T: %w", idRange, err) } if len(jsonlen.Compact(data)) != idRange.jsonLen() { return fmt.Errorf("%T: unexpected JSON length", idRange) diff --git a/fat/fat1/nftokenmetadata.go b/fat/fat1/nftokenmetadata.go index 5302f48..afb03d8 100644 --- a/fat/fat1/nftokenmetadata.go +++ b/fat/fat1/nftokenmetadata.go @@ -42,7 +42,7 @@ func (m *NFTokenIDMetadataMap) UnmarshalJSON(data []byte) error { Metadata json.RawMessage `json:"metadata"` } if err := json.Unmarshal(data, &tknMs); err != nil { - return fmt.Errorf("%T: %v", m, err) + return fmt.Errorf("%T: %w", m, err) } *m = make(NFTokenIDMetadataMap, len(tknMs)) var expectedJSONLen int @@ -55,7 +55,7 @@ func (m *NFTokenIDMetadataMap) UnmarshalJSON(data []byte) error { } var tkns NFTokens if err := tkns.UnmarshalJSON(tknM.Tokens); err != nil { - return fmt.Errorf("%T: %v", m, err) + return fmt.Errorf("%T: %w", m, err) } metadata := jsonlen.Compact(tknM.Metadata) expectedJSONLen += len(metadata) + len(jsonlen.Compact(tknM.Tokens)) diff --git a/fat/fat1/nftokens.go b/fat/fat1/nftokens.go index c10894c..82b5077 100644 --- a/fat/fat1/nftokens.go +++ b/fat/fat1/nftokens.go @@ -223,7 +223,7 @@ func (tkns NFTokens) String() string { func (tkns *NFTokens) UnmarshalJSON(data []byte) error { var tknsJSONAry []json.RawMessage if err := json.Unmarshal(data, &tknsJSONAry); err != nil { - return fmt.Errorf("%T: %v", tkns, err) + return fmt.Errorf("%T: %w", tkns, err) } if len(tknsJSONAry) == 0 { return fmt.Errorf("%T: empty", tkns) @@ -234,18 +234,18 @@ func (tkns *NFTokens) UnmarshalJSON(data []byte) error { if data[0] == '{' { var idRange NFTokenIDRange if err := idRange.UnmarshalJSON(data); err != nil { - return fmt.Errorf("%T: %v", tkns, err) + return fmt.Errorf("%T: %w", tkns, err) } ids = idRange } else { var id NFTokenID if err := json.Unmarshal(data, &id); err != nil { - return fmt.Errorf("%T: %v", tkns, err) + return fmt.Errorf("%T: %w", tkns, err) } ids = id } if err := ids.Set(*tkns); err != nil { - return fmt.Errorf("%T: %v", tkns, err) + return fmt.Errorf("%T: %w", tkns, err) } } return nil diff --git a/fat/fat1/transaction.go b/fat/fat1/transaction.go index 542290c..31bd397 100644 --- a/fat/fat1/transaction.go +++ b/fat/fat1/transaction.go @@ -58,10 +58,10 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { fat.Entry }{} if err := json.Unmarshal(data, &tRaw); err != nil { - return fmt.Errorf("%T: %v", t, err) + return fmt.Errorf("%T: %w", t, err) } if err := t.Inputs.UnmarshalJSON(tRaw.Inputs); err != nil { - return fmt.Errorf("%T.Inputs: %v", t, err) + return fmt.Errorf("%T.Inputs: %w", t, err) } var expectedJSONLen int if len(tRaw.TokenMetadata) > 0 { @@ -70,11 +70,11 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { `invalid field for non-coinbase transaction: "tokenmetadata"`) } if err := t.TokenMetadata.UnmarshalJSON(tRaw.TokenMetadata); err != nil { - return fmt.Errorf("%T.TokenMetadata: %v", t, err) + return fmt.Errorf("%T.TokenMetadata: %w", t, err) } if err := t.TokenMetadata.IsSubsetOf(t.Inputs[fat.Coinbase()]); err != nil { - return fmt.Errorf("%T.TokenMetadata: %v", t, err) + return fmt.Errorf("%T.TokenMetadata: %w", t, err) } expectedJSONLen = len(`,"tokenmetadata":`) + @@ -87,12 +87,12 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { } if err := t.Outputs.UnmarshalJSON(tRaw.Outputs); err != nil { - return fmt.Errorf("%T.Outputs: %v", t, err) + return fmt.Errorf("%T.Outputs: %w", t, err) } t.Metadata = tRaw.Metadata if err := t.ValidData(); err != nil { - return fmt.Errorf("%T: %v", t, err) + return fmt.Errorf("%T: %w", t, err) } expectedJSONLen += len(`{"inputs":,"outputs":}`) + @@ -124,10 +124,10 @@ func (t Transaction) String() string { func (t Transaction) ValidData() error { if err := t.Inputs.NoAddressIntersection(t.Outputs); err != nil { - return fmt.Errorf("Inputs and Outputs intersect: %v", err) + return fmt.Errorf("Inputs and Outputs intersect: %w", err) } if err := t.Inputs.NFTokenIDsConserved(t.Outputs); err != nil { - return fmt.Errorf("Inputs and Outputs mismatch: %v", err) + return fmt.Errorf("Inputs and Outputs mismatch: %w", err) } // Coinbase transactions must only have one input. if t.IsCoinbase() && len(t.Inputs) != 1 { diff --git a/fat/issuance.go b/fat/issuance.go index 21d2a19..4efb517 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -55,10 +55,10 @@ type issuance Issuance func (i *Issuance) UnmarshalJSON(data []byte) error { data = jsonlen.Compact(data) if err := json.Unmarshal(data, (*issuance)(i)); err != nil { - return fmt.Errorf("%T: %v", i, err) + return fmt.Errorf("%T: %w", i, err) } if err := i.ValidData(); err != nil { - return fmt.Errorf("%T: %v", i, err) + return fmt.Errorf("%T: %w", i, err) } if i.expectedJSONLength() != len(data) { return fmt.Errorf("%T: unexpected JSON length", i) diff --git a/fat/type.go b/fat/type.go index ccf31aa..677829b 100644 --- a/fat/type.go +++ b/fat/type.go @@ -42,7 +42,7 @@ func (t *Type) Set(s string) error { num := s[len(format):] var err error if *(*uint64)(t), err = strconv.ParseUint(num, 10, 64); err != nil { - return fmt.Errorf("%T: %v", t, err) + return fmt.Errorf("%T: %w", t, err) } return nil } diff --git a/main.go b/main.go index 2e27035..d47c28e 100644 --- a/main.go +++ b/main.go @@ -44,8 +44,7 @@ func _main() (ret int) { } flag.Validate() - // Set up interrupts channel. We don't want to be interrupted during - // initialization. If the signal is sent we will handle it later. + // Listen for an Interrupt and cancel everything if it occurs. ctx, cancel := context.WithCancel(context.Background()) sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) @@ -82,8 +81,14 @@ func _main() (ret int) { log.Info("Factom Asset Token Daemon started.") - // Stop handling signals once we return. - defer func() { signal.Reset(); close(sigint) }() + defer func() { + // Stop handling all signals so a force quit can occur with a + // second sigint. + signal.Reset() + + // Cause our sigint listener goroutine to call cancel(). + close(sigint) + }() select { case <-ctx.Done(): diff --git a/srv/srv.go b/srv/srv.go index 456ecd0..bc73c2c 100644 --- a/srv/srv.go +++ b/srv/srv.go @@ -96,7 +96,7 @@ func Start(ctx context.Context) (done <-chan struct{}) { err = srv.ListenAndServe() } if err != http.ErrServerClosed { - log.Errorf("srv.ListenAndServe(): %v", err) + log.Errorf("srv.ListenAndServe(): %w", err) } close(_done) }() @@ -108,7 +108,7 @@ func Start(ctx context.Context) (done <-chan struct{}) { context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { - log.Errorf("srv.Shutdown(): %v", err) + log.Errorf("srv.Shutdown(): %w", err) } case <-_done: } From 9cf3893fd2ca4d3696813e85bdd16eb48669d38b Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 7 Oct 2019 15:34:05 -0800 Subject: [PATCH 074/124] refactor(engine,db): Remove unneeded debug output --- db/chain.go | 1 - engine/chainmap.go | 1 - 2 files changed, 2 deletions(-) diff --git a/db/chain.go b/db/chain.go index 1b428ee..c3903a0 100644 --- a/db/chain.go +++ b/db/chain.go @@ -249,7 +249,6 @@ func OpenConnPool(ctx context.Context, dbURI string) ( // Close all database connections. Log any errors. func (chain *Chain) Close() { - chain.Log.Debug("Closing...") chain.Conn.SetInterrupt(nil) sqlitex.ExecScript(chain.Conn, `PRAGMA database.wal_checkpoint;`) if err := chain.Pool.Close(); err != nil { diff --git a/engine/chainmap.go b/engine/chainmap.go index d74c56e..13752aa 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -107,7 +107,6 @@ func (cm ChainMap) setSync(height uint32, dbKeyMR *factom.Bytes32) error { func (cm ChainMap) Close() { defer cm.Unlock() cm.Lock() - log.Debug("Closing all chains...") for _, chain := range cm.m { if chain.IsTracked() { if chain.Pending.Session != nil { From 4d0b7ba4fd641244e74fe7408a90019207bea9f0 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 8 Oct 2019 15:53:38 -0800 Subject: [PATCH 075/124] fix(engine): Fix all race conditions over Chains, ensure snapshot preservation --- engine/chain.go | 33 +++++++++++++++++++++++++++++ engine/chainmap.go | 21 ++++++++++--------- engine/process.go | 52 ++++++++++++++++++++++------------------------ srv/methods.go | 5 ++--- 4 files changed, 71 insertions(+), 40 deletions(-) diff --git a/engine/chain.go b/engine/chain.go index c77ce7c..bcb62c9 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -23,8 +23,10 @@ package engine import ( + "bytes" "context" "fmt" + "sync" "crawshaw.io/sqlite" @@ -40,12 +42,15 @@ type Chain struct { ChainStatus db.Chain + *sync.RWMutex + Pending Pending } type Pending struct { Session *sqlite.Session OfficialSnapshot *sqlite.Snapshot + EndSnapshotRead func() OfficialChain db.Chain Entries map[factom.Bytes32]factom.Entry } @@ -77,6 +82,8 @@ func OpenNew(ctx context.Context, c *factom.Client, return } + chain.RWMutex = new(sync.RWMutex) + var identity factom.Identity identity.ChainID = new(factom.Bytes32) _, *identity.ChainID = fat.TokenIssuer(nameIDs) @@ -184,3 +191,29 @@ func (chain *Chain) Apply(ctx context.Context, c *factom.Client, } return nil } + +func (chain *Chain) revertPending() error { + defer func() { + // Always clean up our session and snapshots. + chain.Pending.EndSnapshotRead() + chain.Pending.Session.Delete() + chain.Pending.Session = nil + chain.Pending.OfficialSnapshot.Free() + chain.Pending.OfficialSnapshot = nil + }() + // Revert all of the pending transactions by applying the inverse of + // the changeset tracked by the session. + var changeset bytes.Buffer + if err := chain.Pending.Session.Changeset(&changeset); err != nil { + return fmt.Errorf("chain.Pending.Session.Changeset(): %w", err) + } + inverse := bytes.NewBuffer(make([]byte, 0, changeset.Len())) + if err := sqlite.ChangesetInvert(inverse, &changeset); err != nil { + return fmt.Errorf("sqlite.ChangesetInvert(): %w", err) + } + if err := chain.Conn.ChangesetApply(inverse, nil, chain.conflictFn); err != nil { + return fmt.Errorf("chain.Conn.ChangesetApply(): %w", err) + + } + return nil +} diff --git a/engine/chainmap.go b/engine/chainmap.go index 13752aa..21f8c17 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -109,13 +109,12 @@ func (cm ChainMap) Close() { cm.Lock() for _, chain := range cm.m { if chain.IsTracked() { - if chain.Pending.Session != nil { - chain.Pending.Session.Delete() - chain.Pending.Session = nil - } + // Rollback any pending entries on the chain. if chain.Pending.OfficialSnapshot != nil { - chain.Pending.OfficialSnapshot.Free() - chain.Pending.OfficialSnapshot = nil + // Always clean up. + if err := chain.revertPending(); err != nil { + log.Error(err) + } } chain.Close() } @@ -162,6 +161,7 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { } chain.Chain = dbChain + chain.RWMutex = new(sync.RWMutex) syncHeight = min(syncHeight, chain.SyncHeight) @@ -172,10 +172,6 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { return } - if err = chain.Sync(ctx, c); err != nil { - dbChains = dbChains[i:] // Close remaining chains. - return - } if !flag.SkipDBValidation { if err = chain.Validate(); err != nil { dbChains = dbChains[i:] // Close remaining chains. @@ -183,6 +179,11 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { } } + if err = chain.Sync(ctx, c); err != nil { + dbChains = dbChains[i:] // Close remaining chains. + return + } + chain.ChainStatus = ChainStatusTracked Chains.trackedIDs = append(Chains.trackedIDs, chain.ID) if chain.Issuance.IsPopulated() { diff --git a/engine/process.go b/engine/process.go index fd6cfb0..c16da14 100644 --- a/engine/process.go +++ b/engine/process.go @@ -23,7 +23,6 @@ package engine import ( - "bytes" "context" "time" @@ -71,6 +70,9 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err return nil } + chain.Lock() + defer chain.Unlock() + // Rollback any pending entries on the chain. if chain.Pending.OfficialSnapshot != nil { // Load any cached entries that are pending and remove them @@ -90,36 +92,18 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err // Delete the entry from the pending cache, rather than // nil the entire map, because this allows the cache - // for various chains to grow according to how active - // they are. But this is not optimal for chains with - // only very occasional bursts of pending entries. + // map size for various chains to grow according to how + // active they are. But this is not optimal for chains + // with only very occasional bursts of pending entries. // // A mechanism to eventually free rarely used pending // entry maps would be a good improvement later. delete(chain.Pending.Entries, *e.Hash) } - // Revert all of the pending transactions by applying the - // inverse of the changeset tracked by the session. - changeset := &bytes.Buffer{} - if err := chain.Pending.Session.Changeset(changeset); err != nil { - return err - } - inverse := bytes.NewBuffer(make([]byte, 0, changeset.Cap())) - if err := sqlite.ChangesetInvert(inverse, changeset); err != nil { + if err := chain.revertPending(); err != nil { return err } - if err := chain.Conn.ChangesetApply( - inverse, nil, chain.conflictFn); err != nil { - return err - } - - // Clean up. - chain.Pending.Session.Delete() - chain.Pending.Session = nil - - chain.Pending.OfficialSnapshot.Free() - chain.Pending.OfficialSnapshot = nil } // prevStatus saves the initial ChainStatus so we can detect if the @@ -149,6 +133,9 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { return nil } + chain.Lock() + defer chain.Unlock() + // Initialize Pending if there is no snapshot yet. if chain.Pending.OfficialSnapshot == nil { // Create the cache if it does not exist. @@ -162,6 +149,16 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { if err != nil { return err } + readConn := chain.Pool.Get(ctx) + endRead, err := readConn.StartSnapshotRead(s) + if err != nil { + return err + } + chain.Pending.EndSnapshotRead = func() { + readConn.SetInterrupt(nil) + endRead() + chain.Pool.Put(readConn) + } chain.Pending.OfficialSnapshot = s chain.Pending.OfficialChain = chain.Chain @@ -229,15 +226,16 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { // release the connection back to the pool. If pending is true, the chain will // reflect the state with pending entries applied. Otherwise the chain will // reflect the official state after the most recent EBlock. -func (chain *Chain) Get(pending bool) func() { +func (chain *Chain) Get(ctx context.Context, pending bool) func() { + chain.RLock() // Pull a Conn off the Pool and set it as the main Conn. - conn := chain.Pool.Get(nil) + conn := chain.Pool.Get(ctx) chain.Conn = conn // If pending or if there is no pending state, then use the chain as // is, and just return a function that returns the conn to the pool. if pending || chain.Pending.OfficialSnapshot == nil { - return func() { chain.Pool.Put(conn) } + return func() { chain.Pool.Put(conn); chain.RUnlock() } } // Use the official chain state with the conn from the Pool. @@ -253,5 +251,5 @@ func (chain *Chain) Get(pending bool) func() { // Return a function that ends the read transaction and returns the // conn to the Pool. - return func() { endRead(); chain.Pool.Put(conn) } + return func() { conn.SetInterrupt(nil); endRead(); chain.Pool.Put(conn); chain.RUnlock() } } diff --git a/srv/methods.go b/srv/methods.go index abad2b5..bb070a8 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -263,7 +263,7 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { balances := make(ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) - put := chain.Get(params.HasIncludePending()) + put := chain.Get(ctx, params.HasIncludePending()) defer put() _, balance, err := addresses.SelectIDBalance(chain.Conn, params.Address) if err != nil { @@ -670,9 +670,8 @@ func validate(ctx context.Context, if !chain.IsIssued() { return nil, nil, ErrorTokenNotFound } - put := chain.Get(params.HasIncludePending()) ctx, cancel := context.WithTimeout(ctx, time.Second) - chain.Conn.SetInterrupt(ctx.Done()) + put := chain.Get(ctx, params.HasIncludePending()) return &chain, func() { cancel(); put() }, nil } return nil, nil, nil From d13ce5c7688d3c9df02220a8704d69e45f3e9fcd Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 8 Oct 2019 16:04:57 -0800 Subject: [PATCH 076/124] docs(engine): Add comment to clarify snapshot creation --- engine/process.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/process.go b/engine/process.go index c16da14..108137b 100644 --- a/engine/process.go +++ b/engine/process.go @@ -149,6 +149,11 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { if err != nil { return err } + + // SQLite does not guanratee that snapshots will remain + // readable over time due to automatic WAL checkpoints. Thus we + // must keep a read transaction open on the snapshot to prevent + // the WAL autocheckpoints from going past the snapshot. readConn := chain.Pool.Get(ctx) endRead, err := readConn.StartSnapshotRead(s) if err != nil { From 68c608a734bdf1cebc9ae3863743dce47c054fe0 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 8 Oct 2019 16:06:36 -0800 Subject: [PATCH 077/124] docs(engine): Explain why we clear the interrupt --- engine/process.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/engine/process.go b/engine/process.go index 108137b..75499f9 100644 --- a/engine/process.go +++ b/engine/process.go @@ -160,6 +160,8 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { return err } chain.Pending.EndSnapshotRead = func() { + // We must clear the interrupt to prevent endRead from + // panicking. readConn.SetInterrupt(nil) endRead() chain.Pool.Put(readConn) @@ -256,5 +258,12 @@ func (chain *Chain) Get(ctx context.Context, pending bool) func() { // Return a function that ends the read transaction and returns the // conn to the Pool. - return func() { conn.SetInterrupt(nil); endRead(); chain.Pool.Put(conn); chain.RUnlock() } + return func() { + // We must clear the interrupt to prevent endRead from + // panicking. + conn.SetInterrupt(nil) + endRead() + chain.Pool.Put(conn) + chain.RUnlock() + } } From bad2d4765d4739704dee824ab05b89edf1ccd435 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 8 Oct 2019 18:44:34 -0800 Subject: [PATCH 078/124] fix(db,engine): Declare PRIMARY KEY on nf_token_transactions to allow Session tracking --- db/nftokens/txrelation.go | 2 +- db/validate.go | 1 + engine/chain.go | 9 +++++++++ engine/process.go | 15 +++++++-------- go.mod | 4 +++- go.sum | 4 ++-- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/db/nftokens/txrelation.go b/db/nftokens/txrelation.go index a78f09f..17850e9 100644 --- a/db/nftokens/txrelation.go +++ b/db/nftokens/txrelation.go @@ -14,7 +14,7 @@ const CreateTableTransactions = `CREATE TABLE "nf_token_transactions" ( "adr_tx_id" INTEGER NOT NULL, "nf_tkn_id" INTEGER NOT NULL, - UNIQUE("adr_tx_id", "nf_tkn_id"), + PRIMARY KEY("adr_tx_id", "nf_tkn_id"), FOREIGN KEY("nf_tkn_id") REFERENCES "nf_tokens", FOREIGN KEY("adr_tx_id") REFERENCES "address_transactions" diff --git a/db/validate.go b/db/validate.go index 813cb64..e24ae2a 100644 --- a/db/validate.go +++ b/db/validate.go @@ -68,6 +68,7 @@ func (chain Chain) Validate() (err error) { if err != nil { return err } + sess.Attach("eblocks") sess.Attach("entries") sess.Attach("addresses") sess.Attach("nf_tokens") diff --git a/engine/chain.go b/engine/chain.go index bcb62c9..b608aa7 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -192,7 +192,15 @@ func (chain *Chain) Apply(ctx context.Context, c *factom.Client, return nil } +func (chain *Chain) conflictFn( + cType sqlite.ConflictType, _ sqlite.ChangesetIter) sqlite.ConflictAction { + chain.Log.Errorf("ChangesetApply Conflict: %v", cType) + return sqlite.SQLITE_CHANGESET_ABORT +} func (chain *Chain) revertPending() error { + // We must clear the interrupt to prevent from panicking or being + // interrupted while reverting. + oldDone := chain.SetInterrupt(nil) defer func() { // Always clean up our session and snapshots. chain.Pending.EndSnapshotRead() @@ -200,6 +208,7 @@ func (chain *Chain) revertPending() error { chain.Pending.Session = nil chain.Pending.OfficialSnapshot.Free() chain.Pending.OfficialSnapshot = nil + chain.SetInterrupt(oldDone) }() // Revert all of the pending transactions by applying the inverse of // the changeset tracked by the session. diff --git a/engine/process.go b/engine/process.go index 75499f9..40de76e 100644 --- a/engine/process.go +++ b/engine/process.go @@ -26,7 +26,6 @@ import ( "context" "time" - "crawshaw.io/sqlite" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/flag" @@ -101,7 +100,12 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err delete(chain.Pending.Entries, *e.Hash) } - if err := chain.revertPending(); err != nil { + err := chain.revertPending() + // We must save the Chain back to the map at this point to + // avoid a double free panic in the event of any further + // errors. + Chains.set(chain.ID, chain, chain.ChainStatus) + if err != nil { return err } } @@ -119,11 +123,6 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err Chains.set(chain.ID, chain, prevStatus) return nil } -func (chain Chain) conflictFn( - cType sqlite.ConflictType, _ sqlite.ChangesetIter) sqlite.ConflictAction { - chain.Log.Errorf("ChangesetApply Conflict: %v", cType) - return sqlite.SQLITE_CHANGESET_ABORT -} func ProcessPending(ctx context.Context, es ...factom.Entry) error { chain := Chains.Get(es[0].ChainID) @@ -160,7 +159,7 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { return err } chain.Pending.EndSnapshotRead = func() { - // We must clear the interrupt to prevent endRead from + // We must clear the interrupt to prevent from // panicking. readConn.SetInterrupt(nil) endRead() diff --git a/go.mod b/go.mod index 40dc251..bf86412 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,8 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191007030329-a2a45e244691 +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23 + +//replace crawshaw.io/sqlite => /home/aslevy/repos/go-modules/AdamSLevy/sqlite //replace github.com/Factom-Asset-Tokens/factom => ../factom diff --git a/go.sum b/go.sum index d081967..8897afb 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmK github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20191007030329-a2a45e244691 h1:b9infNdfhwgJJ1pMinwdyfq8tS74094DcT4v/iHk36U= -github.com/AdamSLevy/sqlite v0.1.3-0.20191007030329-a2a45e244691/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23 h1:rgBx93rHyTKWrAYaRfaD6DZcc6ezXflPtnxRlsG1el4= +github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d h1:yPm4An70OhM4k4WUq7M9sWaVlFas2+hJB+I3Fsgw38A= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= From deff0a4659b4fda77ba5e450ca056b81fe7b27db Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 8 Oct 2019 22:59:03 -0800 Subject: [PATCH 079/124] fix(srv): Use proper ChainID in "send-transaction" params --- srv/params.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srv/params.go b/srv/params.go index 256cacb..ced8cae 100644 --- a/srv/params.go +++ b/srv/params.go @@ -256,7 +256,7 @@ type ParamsSendTransaction struct { func (p *ParamsSendTransaction) IsValid() error { if p.Raw != nil { if p.ExtIDs != nil || p.Content != nil || - p.ParamsToken != (ParamsToken{}) { + p.ParamsToken.IsValid() == nil { return jsonrpc2.ErrorInvalidParams( `"raw cannot be used with "content" or "extids"`) } @@ -278,7 +278,7 @@ func (p *ParamsSendTransaction) IsValid() error { ExtIDs: p.ExtIDs, Content: p.Content, Timestamp: time.Now(), - ChainID: p.ChainID, + ChainID: p.ValidChainID(), } data, err := p.entry.MarshalBinary() From 35f8262c00aef60ada4466fa8cce1a4a9ef2f0db Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 9 Oct 2019 13:21:03 -0800 Subject: [PATCH 080/124] fix(engine): Fix potential double close panic during a canceled startup --- engine/chainmap.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/engine/chainmap.go b/engine/chainmap.go index 21f8c17..bc97d6e 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -132,9 +132,10 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { defer func() { if err != nil { for _, chain := range dbChains { - chain.Close() + if chain.Conn != nil { + chain.Close() + } } - Chains.Close() } }() @@ -153,10 +154,12 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { for i, dbChain := range dbChains { chain := Chains.m[*dbChain.ID] - // Skip blacklisted chains or if there was a whitelist, any - // non-tracked chain. + // Close and skip any blacklisted chains or, if there was a + // whitelist, any non-tracked chain. if chain.IsIgnored() || flag.HasWhitelist() && !chain.IsTracked() { dbChain.Close() + // Prevent double close in defer on error. + dbChains[i].Conn = nil continue } @@ -166,7 +169,6 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { syncHeight = min(syncHeight, chain.SyncHeight) if chain.NetworkID != flag.NetworkID { - dbChains = dbChains[i:] // Close remaining chains. err = fmt.Errorf("invalid NetworkID: %v for Chain{%v}", chain.NetworkID, chain.ID) return @@ -174,13 +176,11 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { if !flag.SkipDBValidation { if err = chain.Validate(); err != nil { - dbChains = dbChains[i:] // Close remaining chains. return } } if err = chain.Sync(ctx, c); err != nil { - dbChains = dbChains[i:] // Close remaining chains. return } @@ -194,8 +194,6 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { Chains.m[*chain.ID] = chain } - dbChains = nil // Prevent closing any chains from this list. - // Open any whitelisted chains that do not already have databases. for id, chain := range Chains.m { if chain.IsIgnored() || chain.Chain.Conn != nil { @@ -209,6 +207,10 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { Chains.issuedIDs = append(Chains.issuedIDs, chain.ID) } Chains.m[*chain.ID] = chain + + // Ensure that this new chain gets closed in the defer if an + // error occurs. + dbChains = append(dbChains, chain.Chain) } return From b6185c5f51d37ee5d1b59e6b4d148c537e8d7a0f Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 9 Oct 2019 17:10:07 -0800 Subject: [PATCH 081/124] refactor(engine): Add newline --- engine/engine.go | 1 + revision | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/engine.go b/engine/engine.go index 7c84c70..8e9a903 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -264,6 +264,7 @@ func engine(ctx context.Context, done chan struct{}) { for _, eb := range dblock.EBlocks { eblocks <- eb } + // Wait for all EBlocks to be processed. eblocksWG.Wait() diff --git a/revision b/revision index 796b6bc..33fe397 100755 --- a/revision +++ b/revision @@ -24,7 +24,9 @@ gitver() { ( set -o pipefail - git describe --long --tags 2>/dev/null | sed 's/\([^-]*-g\)/r\1/;s/-/./g' | tr -d '\n' || + git describe --long --tags 2>/dev/null | + sed 's/\([^-]*-g\)/r\1/;s/-/./g' | + tr -d '\n' || printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" ) } From ff2caf676c8e08dd25768b14602495ab4f3bea07 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 10 Oct 2019 12:37:04 -0800 Subject: [PATCH 082/124] fix(engine): Prevent new whitelisted Chains from being GC'd fix #43 --- engine/chain.go | 28 +++++++++++++++++++--------- engine/chainmap.go | 6 ++++-- engine/process.go | 2 ++ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/engine/chain.go b/engine/chain.go index b608aa7..9744688 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -109,35 +109,45 @@ func OpenNew(ctx context.Context, c *factom.Client, } else { chain.ChainStatus = ChainStatusTracked } + chain.RWMutex = new(sync.RWMutex) return } -func (chain *Chain) OpenNewByChainID(ctx context.Context, - c *factom.Client, chainID *factom.Bytes32) error { +func OpenNewByChainID(ctx context.Context, + c *factom.Client, chainID *factom.Bytes32) (chain Chain, err error) { eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(ctx, c) if err != nil { - return fmt.Errorf("factom.EBlock.GetPrevAll(): %w", err) + err = fmt.Errorf("factom.EBlock.GetPrevAll(): %w", err) + return } first := eblocks[len(eblocks)-1] // Get DBlock Timestamp and KeyMR var dblock factom.DBlock dblock.Height = first.Height - if err := dblock.Get(ctx, c); err != nil { - return fmt.Errorf("factom.DBlock.Get(): %w", err) + if err = dblock.Get(ctx, c); err != nil { + err = fmt.Errorf("factom.DBlock.Get(): %w", err) + return } first.SetTimestamp(dblock.Timestamp) - *chain, err = OpenNew(ctx, c, dblock.KeyMR, first) + chain, err = OpenNew(ctx, c, dblock.KeyMR, first) if err != nil { - return err + return } + defer func() { + if err != nil { + chain.Close() + } + }() if chain.IsUnknown() { - return fmt.Errorf("not a valid FAT chain: %v", chainID) + err = fmt.Errorf("not a valid FAT chain: %v", chainID) + return } // We already applied the first EBlock. Sync the remaining. - return chain.SyncEBlocks(ctx, c, eblocks[:len(eblocks)-1]) + err = chain.SyncEBlocks(ctx, c, eblocks[:len(eblocks)-1]) + return } func (chain *Chain) Sync(ctx context.Context, c *factom.Client) error { diff --git a/engine/chainmap.go b/engine/chainmap.go index bc97d6e..9297d53 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -196,10 +196,12 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { // Open any whitelisted chains that do not already have databases. for id, chain := range Chains.m { - if chain.IsIgnored() || chain.Chain.Conn != nil { + if !chain.IsTracked() || chain.Chain.Conn != nil { continue } - if err = chain.OpenNewByChainID(ctx, c, &id); err != nil { + var chain Chain + chain, err = OpenNewByChainID(ctx, c, &id) + if err != nil { return } Chains.trackedIDs = append(Chains.trackedIDs, chain.ID) diff --git a/engine/process.go b/engine/process.go index 40de76e..21e4866 100644 --- a/engine/process.go +++ b/engine/process.go @@ -74,6 +74,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err // Rollback any pending entries on the chain. if chain.Pending.OfficialSnapshot != nil { + chain.Log.Debug("Cleaning up pending state...") // Load any cached entries that are pending and remove them // from the cache. for i := range eb.Entries { @@ -137,6 +138,7 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { // Initialize Pending if there is no snapshot yet. if chain.Pending.OfficialSnapshot == nil { + chain.Log.Debug("Initializing pending...") // Create the cache if it does not exist. if chain.Pending.Entries == nil { chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) From 4e6aac55d10c481b47dc32a83a728b5fb2396df0 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 9 Oct 2019 17:11:17 -0800 Subject: [PATCH 083/124] feat(fat): Add initial support for defining precision in fat.Issuance --- cli/cmd/getbalance.go | 24 ++++++++++++++++++++++-- cli/cmd/getchains.go | 1 + cli/cmd/issue.go | 4 +++- fat/issuance.go | 34 +++++++++++++++++++++++++--------- fat/type.go | 6 +++++- 5 files changed, 56 insertions(+), 13 deletions(-) diff --git a/cli/cmd/getbalance.go b/cli/cmd/getbalance.go index 8786367..eb39147 100644 --- a/cli/cmd/getbalance.go +++ b/cli/cmd/getbalance.go @@ -115,11 +115,26 @@ func getBalance(cmd *cobra.Command, _ []string) { } fmt.Println() for chainID, balance := range balances { - fmt.Printf("\t%v: %v\n", chainID, balance) + vrbLog.Printf("Fetching token chain details... %v", chainID) + params := srv.ParamsToken{ChainID: &chainID} + var stats srv.ResultGetStats + if err := FATClient.Request(context.Background(), + "get-stats", params, &stats); err != nil { + errLog.Fatal(err) + } + var bal interface{} + if stats.Issuance.Precision > 1 { + bal = float64(balance) / math.Pow10( + int(stats.Issuance.Precision)) + } else { + bal = balance + } + fmt.Printf("\t%v: %v\n", chainID, bal) } } return } + vrbLog.Printf("Fetching token chain details... %v", paramsToken.ChainID) params := srv.ParamsToken{ChainID: paramsToken.ChainID} var stats srv.ResultGetStats @@ -140,7 +155,12 @@ func getBalance(cmd *cobra.Command, _ []string) { "get-balance", params, &balance); err != nil { errLog.Fatal(err) } - fmt.Println(adr, balance) + if stats.Issuance.Precision > 1 { + fmt.Println(adr, float64(balance)/math.Pow10( + int(stats.Issuance.Precision))) + } else { + fmt.Println(adr, balance) + } } case fat1.Type: var params srv.ParamsGetNFBalance diff --git a/cli/cmd/getchains.go b/cli/cmd/getchains.go index 4180f81..4a726c3 100644 --- a/cli/cmd/getchains.go +++ b/cli/cmd/getchains.go @@ -134,6 +134,7 @@ Issuance Entry Hash: %v Token ID: %v Type: %v Symbol: %q +Precision: %q Supply: %v Ciculating Supply: %v Burned: %v diff --git a/cli/cmd/issue.go b/cli/cmd/issue.go index f429a3c..79fb0fc 100644 --- a/cli/cmd/issue.go +++ b/cli/cmd/issue.go @@ -123,7 +123,9 @@ Entry Credits "Token standard to use").DefValue = "" flags.VarPF(&sk1, "sk1", "", "Secret Identity Key 1 to sign entry").DefValue = "" flags.Int64Var(&Issuance.Supply, "supply", 0, - "Max Token supply, use -1 for unlimited") + "Max Token supply in base units, use -1 for unlimited") + flags.UintVar(&Issuance.Precision, "precision", 0, + "Number of whole unit decimal places, \ni.e. 3 means 1.0 is 1000 base units") flags.StringVar(&Issuance.Symbol, "symbol", "", "Optional abbreviated token symbol") flags.VarPF((*RawMessage)(&Issuance.Metadata), "metadata", "m", "JSON metadata to include in tx") diff --git a/fat/issuance.go b/fat/issuance.go index 4efb517..7a91b42 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -41,10 +41,13 @@ func Coinbase() factom.FAAddress { return coinbase } +const MaxPrecision = 18 + // Issuance represents the Issuance of a token. type Issuance struct { - Type Type `json:"type"` - Supply int64 `json:"supply"` + Type Type `json:"type"` + Supply int64 `json:"supply"` + Precision uint `json:"precision,omitempty"` Symbol string `json:"symbol,omitempty"` Entry @@ -69,16 +72,15 @@ func (i Issuance) expectedJSONLength() int { l := len(`{}`) l += len(`"type":""`) + len(i.Type.String()) l += len(`,"supply":`) + jsonlen.Int64(i.Supply) - l += jsonStrLen("symbol", i.Symbol) + if i.Precision != 0 { + l += len(`,"precision":`) + jsonlen.Uint64(uint64(i.Precision)) + } + if len(i.Symbol) > 0 { + l += len(`,"symbol":""`) + len(i.Symbol) + } l += i.MetadataJSONLen() return l } -func jsonStrLen(name, value string) int { - if len(value) == 0 { - return 0 - } - return len(`,"":""`) + len(name) + len(value) -} func (i Issuance) MarshalJSON() ([]byte, error) { if err := i.ValidData(); err != nil { @@ -126,6 +128,20 @@ func (i Issuance) ValidData() error { if i.Supply == 0 || i.Supply < -1 { return fmt.Errorf(`invalid "supply": must be positive or -1`) } + switch i.Type { + case TypeFAT0: + if i.Precision > MaxPrecision { + return fmt.Errorf( + `invalid "precision": must be less than 18`) + } + case TypeFAT1: + if i.Precision != 0 { + return fmt.Errorf( + `invalid "precision": not allowed for %v`, i.Type) + } + default: + panic(i.Type.String()) + } return nil } diff --git a/fat/type.go b/fat/type.go index 677829b..c7dbb0b 100644 --- a/fat/type.go +++ b/fat/type.go @@ -60,7 +60,11 @@ func (t Type) MarshalJSON() ([]byte, error) { } func (t Type) String() string { - return fmt.Sprintf("FAT-%v", uint64(t)) + fmtStr := "FAT-%v" + if !t.IsValid() { + fmtStr = "invalid fat.Type: %v" + } + return fmt.Sprintf(fmtStr, uint64(t)) } func (t Type) IsValid() bool { From c94520bf31ad454cc2cb3de81dba2aba029259ec Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 10 Oct 2019 13:35:43 -0800 Subject: [PATCH 084/124] build(Makefile): Add -trimpath to go build for reproducible builds --- Makefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index ad014e1..7acbac4 100644 --- a/Makefile +++ b/Makefile @@ -43,37 +43,37 @@ SRC = $(DEPSRC) $(filter-out %_test.go,$(wildcard *.go */*.go */*/*.go)) FATDSRC=$(filter-out cli/%,$(SRC)) $(GENSRC) fatd: $(FATDSRC) - go build -ldflags=$(FATD_LDFLAGS) ./ + go build -trimpath -ldflags=$(FATD_LDFLAGS) ./ CLISRC=$(filter-out main.go engine/% state/% flag/%,$(SRC)) $(GENSRC) fat-cli: $(CLISRC) - go build -ldflags=$(CLI_LDFLAGS) -o fat-cli ./cli + go build -trimpath -ldflags=$(CLI_LDFLAGS) -o fat-cli ./cli fatd.race: $(FATDSRC) - go build -race -ldflags=$(FATD_LDFLAGS) -o fatd.race ./ + go build -trimpath -race -ldflags=$(FATD_LDFLAGS) -o fatd.race ./ fat-cli.race: $(CLISRC) - go build -race -ldflags=$(CLI_LDFLAGS) -o fat-cli.race ./cli + go build -trimpath -race -ldflags=$(CLI_LDFLAGS) -o fat-cli.race ./cli fatd.mac: $(FATDSRC) - env GOOS=darwin GOARCH=amd64 go build -ldflags=$(FATD_LDFLAGS) -o fatd.mac ./ + env GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags=$(FATD_LDFLAGS) -o fatd.mac ./ fatd.exe: $(FATDSRC) - env GOOS=windows GOARCH=amd64 go build -ldflags=$(FATD_LDFLAGS) -o fatd.exe ./ + env GOOS=windows GOARCH=amd64 go build -trimpath -ldflags=$(FATD_LDFLAGS) -o fatd.exe ./ fatd-linux: $(FATDSRC) - env GOOS=linux GOARCH=amd64 go build -ldflags=$(FATD_LDFLAGS) ./ + env GOOS=linux GOARCH=amd64 go build -trimpath -ldflags=$(FATD_LDFLAGS) ./ fat-cli.mac: $(CLISRC) - env GOOS=darwin GOARCH=amd64 go build -ldflags=$(CLI_LDFLAGS) -o fat-cli.mac ./ + env GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags=$(CLI_LDFLAGS) -o fat-cli.mac ./ fat-cli.exe: $(CLISRC) - env GOOS=windows GOARCH=amd64 go build -ldflags=$(CLI_LDFLAGS) -o fat-cli.exe ./ + env GOOS=windows GOARCH=amd64 go build -trimpath -ldflags=$(CLI_LDFLAGS) -o fat-cli.exe ./ fat-cli-linux: $(CLISRC) - env GOOS=linux GOARCH=amd64 go build -ldflags=$(CLI_LDFLAGS) -o fat-cli ./cli + env GOOS=linux GOARCH=amd64 go build -trimpath -ldflags=$(CLI_LDFLAGS) -o fat-cli ./cli .PHONY: clean clean-gen purge-db unpurge-db From cfa0e15ee56f8b52310188615b90af49d2772d8c Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 10 Oct 2019 13:36:33 -0800 Subject: [PATCH 085/124] refactor(all): Never use := for zero value declaration --- cli/cmd/getbalance.go | 6 +++--- cli/cmd/gettransactions.go | 4 ++-- engine/engine.go | 2 +- fat/fat0/addressamountmap.go | 4 +++- fat/fat0/transaction_test.go | 10 +++++----- fat/fat1/addressnftokensmap.go | 4 +++- fat/fat1/addressnftokensmap_test.go | 2 +- fat/fat1/transaction_test.go | 10 +++++----- fat/issuance.go | 5 +---- srv/methods.go | 20 ++++++++++---------- 10 files changed, 34 insertions(+), 33 deletions(-) diff --git a/cli/cmd/getbalance.go b/cli/cmd/getbalance.go index eb39147..257debc 100644 --- a/cli/cmd/getbalance.go +++ b/cli/cmd/getbalance.go @@ -108,7 +108,7 @@ func getBalance(cmd *cobra.Command, _ []string) { "get-balances", params, &balances); err != nil { errLog.Fatal(err) } - fmt.Printf("%v:", adr) + fmt.Printf("%v ", adr) if len(balances) == 0 { fmt.Println(" none") continue @@ -129,7 +129,7 @@ func getBalance(cmd *cobra.Command, _ []string) { } else { bal = balance } - fmt.Printf("\t%v: %v\n", chainID, bal) + fmt.Printf("\t%v %v\n", chainID, bal) } } return @@ -144,7 +144,7 @@ func getBalance(cmd *cobra.Command, _ []string) { } switch stats.Issuance.Type { case fat0.Type: - params := srv.ParamsGetBalance{} + var params srv.ParamsGetBalance params.ChainID = paramsToken.ChainID params.IncludePending = paramsToken.IncludePending vrbLog.Println("Fetching balances...") diff --git a/cli/cmd/gettransactions.go b/cli/cmd/gettransactions.go index e67d955..5adf2da 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/cmd/gettransactions.go @@ -196,8 +196,8 @@ func getTxs(_ *cobra.Command, _ []string) { return } params := srv.ParamsGetTransaction{ParamsToken: paramsGetTxs.ParamsToken} - result := srv.ResultGetTransaction{} - tx := json.RawMessage{} + var result srv.ResultGetTransaction + var tx json.RawMessage result.Tx = &tx for _, txID := range transactionIDs { vrbLog.Printf("Fetching tx details... %v", txID) diff --git a/engine/engine.go b/engine/engine.go index 8e9a903..9f813fd 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -192,7 +192,7 @@ func engine(ctx context.Context, done chan struct{}) { // eblocksWG is used to signal that all EBlocks for the current DBlock // are done being processed. This is reused each DBlock. - eblocksWG := &sync.WaitGroup{} + var eblocksWG sync.WaitGroup // stopWorkers may be called multiple times by any worker or this // goroutine, but eblocks will only ever be closed once. diff --git a/fat/fat0/addressamountmap.go b/fat/fat0/addressamountmap.go index ef1a068..89d5386 100644 --- a/fat/fat0/addressamountmap.go +++ b/fat/fat0/addressamountmap.go @@ -51,6 +51,8 @@ func (m AddressAmountMap) MarshalJSON() ([]byte, error) { return json.Marshal(adrStrAmountMap) } +var adrStrLen = len(factom.FAAddress{}.String()) + // UnmarshalJSON unmarshals a list of addresses and amounts used in the inputs // or outputs of a transaction. Duplicate addresses or addresses with a 0 // amount cause an error. @@ -62,7 +64,7 @@ func (m *AddressAmountMap) UnmarshalJSON(data []byte) error { if len(adrStrAmountMap) == 0 { return fmt.Errorf("%T: empty", m) } - adrJSONLen := len(`"":,`) + len(factom.FAAddress{}.String()) + adrJSONLen := len(`"":,`) + adrStrLen expectedJSONLen := len(`{}`) - len(`,`) + len(adrStrAmountMap)*adrJSONLen *m = make(AddressAmountMap, len(adrStrAmountMap)) var adr factom.FAAddress diff --git a/fat/fat0/transaction_test.go b/fat/fat0/transaction_test.go index e46c9fd..9435d3c 100644 --- a/fat/fat0/transaction_test.go +++ b/fat/fat0/transaction_test.go @@ -294,21 +294,21 @@ func validCoinbaseTxEntryContentMap() map[string]interface{} { // inputs/outputs func inputs() map[string]uint64 { - inputs := map[string]uint64{} + var inputs map[string]uint64 for i := range inputAddresses { inputs[inputAddresses[i].FAAddress().String()] = inputAmounts[i] } return inputs } func outputs() map[string]uint64 { - outputs := map[string]uint64{} + var outputs map[string]uint64 for i := range outputAddresses { outputs[outputAddresses[i].FAAddress().String()] = outputAmounts[i] } return outputs } func coinbaseInputs() map[string]uint64 { - inputs := map[string]uint64{} + var inputs map[string]uint64 for i := range coinbaseInputAddresses { inputs[coinbaseInputAddresses[i].FAAddress().String()] = coinbaseInputAmounts[i] @@ -316,7 +316,7 @@ func coinbaseInputs() map[string]uint64 { return inputs } func coinbaseOutputs() map[string]uint64 { - outputs := map[string]uint64{} + var outputs map[string]uint64 for i := range coinbaseOutputAddresses { outputs[coinbaseOutputAddresses[i].FAAddress().String()] = coinbaseOutputAmounts[i] @@ -402,7 +402,7 @@ func outputAddressAmountMap() AddressAmountMap { func addressAmountMap(aas map[string]uint64) AddressAmountMap { m := make(AddressAmountMap) for addressStr, amount := range aas { - a := factom.FAAddress{} + var a factom.FAAddress if err := json.Unmarshal( []byte(fmt.Sprintf("%#v", addressStr)), &a); err != nil { panic(err) diff --git a/fat/fat1/addressnftokensmap.go b/fat/fat1/addressnftokensmap.go index 5f77ccd..a7abb74 100644 --- a/fat/fat1/addressnftokensmap.go +++ b/fat/fat1/addressnftokensmap.go @@ -51,6 +51,8 @@ func (m AddressNFTokensMap) MarshalJSON() ([]byte, error) { return json.Marshal(adrStrTknsMap) } +var adrStrLen = len(factom.FAAddress{}.String()) + func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { var adrStrDataMap map[string]json.RawMessage if err := json.Unmarshal(data, &adrStrDataMap); err != nil { @@ -59,7 +61,7 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { if len(adrStrDataMap) == 0 { return fmt.Errorf("%T: empty", m) } - adrJSONLen := len(`"":,`) + len(factom.FAAddress{}.String()) + adrJSONLen := len(`"":,`) + adrStrLen expectedJSONLen := len(`{}`) - len(`,`) + len(adrStrDataMap)*adrJSONLen *m = make(AddressNFTokensMap, len(adrStrDataMap)) var adr factom.FAAddress diff --git a/fat/fat1/addressnftokensmap_test.go b/fat/fat1/addressnftokensmap_test.go index 72caf8f..8bda966 100644 --- a/fat/fat1/addressnftokensmap_test.go +++ b/fat/fat1/addressnftokensmap_test.go @@ -153,7 +153,7 @@ func TestAddressNFTokensMapUnmarshal(t *testing.T) { for _, test := range AddressNFTokensMapUnmarshalTests { t.Run(test.Name, func(t *testing.T) { assert := assert.New(t) - adrNFTkns := AddressNFTokensMap{} + var adrNFTkns AddressNFTokensMap err := adrNFTkns.UnmarshalJSON([]byte(test.JSON)) if len(test.Error) > 0 { assert.Contains(err.Error(), test.Error) diff --git a/fat/fat1/transaction_test.go b/fat/fat1/transaction_test.go index eb2322c..58eedc6 100644 --- a/fat/fat1/transaction_test.go +++ b/fat/fat1/transaction_test.go @@ -320,7 +320,7 @@ func validCoinbaseTxEntryContentMap() map[string]interface{} { // inputs/outputs func inputs() map[string]NFTokens { - inputs := map[string]NFTokens{} + var inputs map[string]NFTokens for i := range inputAddresses { tkns := newNFTokens() tkns.Append(inputNFTokens[i]) @@ -329,7 +329,7 @@ func inputs() map[string]NFTokens { return inputs } func outputs() map[string]NFTokens { - outputs := map[string]NFTokens{} + var outputs map[string]NFTokens for i := range outputAddresses { tkns := newNFTokens() tkns.Append(outputNFTokens[i]) @@ -338,7 +338,7 @@ func outputs() map[string]NFTokens { return outputs } func coinbaseInputs() map[string]NFTokens { - inputs := map[string]NFTokens{} + var inputs map[string]NFTokens for i := range coinbaseInputAddresses { tkns := newNFTokens() tkns.Append(coinbaseInputNFTokens[i]) @@ -347,7 +347,7 @@ func coinbaseInputs() map[string]NFTokens { return inputs } func coinbaseOutputs() map[string]NFTokens { - outputs := map[string]NFTokens{} + var outputs map[string]NFTokens for i := range coinbaseOutputAddresses { tkns := newNFTokens() tkns.Append(coinbaseOutputNFTokens[i]) @@ -425,7 +425,7 @@ func outputAddressNFTokensMap() AddressNFTokensMap { func addressNFTokensMap(aas map[string]NFTokens) AddressNFTokensMap { m := make(AddressNFTokensMap) for adrStr, amount := range aas { - a := factom.FAAddress{} + var a factom.FAAddress if err := a.Set(adrStr); err != nil { panic(err.Error() + " " + adrStr) } diff --git a/fat/issuance.go b/fat/issuance.go index 7a91b42..49ec1db 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -31,10 +31,7 @@ import ( ) var ( - coinbase = func() factom.FAAddress { - priv := factom.FsAddress{} - return priv.FAAddress() - }() + coinbase = factom.FsAddress{}.FAAddress() ) func Coinbase() factom.FAAddress { diff --git a/srv/methods.go b/srv/methods.go index bb070a8..fdf8d16 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -74,7 +74,7 @@ type ResultGetIssuance struct { func getIssuance(entry bool) jsonrpc2.MethodFunc { return func(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsToken{} + var params ParamsToken chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -106,7 +106,7 @@ type ResultGetTransaction struct { func getTransaction(getEntry bool) jsonrpc2.MethodFunc { return func(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetTransaction{} + var params ParamsGetTransaction chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -150,7 +150,7 @@ func getTransaction(getEntry bool) jsonrpc2.MethodFunc { func getTransactions(getEntry bool) jsonrpc2.MethodFunc { return func(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetTransactions{} + var params ParamsGetTransactions chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -213,7 +213,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { } func getBalance(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetBalance{} + var params ParamsGetBalance chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -254,7 +254,7 @@ func (r *ResultGetBalances) UnmarshalJSON(data []byte) error { } func getBalances(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetBalances{} + var params ParamsGetBalances if _, _, err := validate(ctx, data, ¶ms); err != nil { return err } @@ -277,7 +277,7 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { } func getNFBalance(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetNFBalance{} + var params ParamsGetNFBalance chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -320,7 +320,7 @@ type ResultGetStats struct { var coinbaseRCDHash = fat.Coinbase() func getStats(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsToken{} + var params ParamsToken chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -369,7 +369,7 @@ type ResultGetNFToken struct { } func getNFToken(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetNFToken{} + var params ParamsGetNFToken chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -408,7 +408,7 @@ func getNFToken(ctx context.Context, data json.RawMessage) interface{} { } func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsGetAllNFTokens{} + var params ParamsGetAllNFTokens chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -443,7 +443,7 @@ func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { } func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { - params := ParamsSendTransaction{} + var params ParamsSendTransaction chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err From 36f03f00a1684bea43ca82ff949ad5f4d126cdfc Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 10 Oct 2019 19:46:03 -0800 Subject: [PATCH 086/124] fix(engine): Shadow id for use with chain.ID in whitelist for loop fix #47 #46 #43 --- cli/cmd/issue.go | 2 +- db/apply.go | 24 ++--- db/chain.go | 5 +- db/validate.go | 2 +- engine/chain.go | 87 ++++++------------ engine/chainmap.go | 35 +++---- engine/engine.go | 12 ++- engine/process.go | 225 +++++++++++++++++++++++++++++++-------------- go.mod | 4 +- go.sum | 6 +- srv/methods.go | 2 +- 11 files changed, 235 insertions(+), 169 deletions(-) diff --git a/cli/cmd/issue.go b/cli/cmd/issue.go index 79fb0fc..bcaa443 100644 --- a/cli/cmd/issue.go +++ b/cli/cmd/issue.go @@ -325,7 +325,7 @@ func issueChain(_ *cobra.Command, _ []string) { vrbLog.Println("Submitting the Chain Creation Entry to the Factom blockchain...") txID, err := first.ComposeCreate(context.Background(), FactomClient, ecEsAdr.Es) if err != nil { - errLog.Fatal(err) + errLog.Fatal(fmt.Errorf("factom.Entry.ComposeCreate(): %w", err)) } fmt.Println("Chain Creation Entry Submitted") fmt.Println("Chain ID: ", first.ChainID) diff --git a/db/apply.go b/db/apply.go index 9eb06ab..948a271 100644 --- a/db/apply.go +++ b/db/apply.go @@ -87,12 +87,12 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error if err != nil { return } - chain.Log.Debugf("Entry{%v}: invalid Issuance: %v", - e.Hash, issueErr) + //chain.Log.Debugf("Entry{%v}: invalid Issuance: %v", + // e.Hash, issueErr) return } rollback(&err) // commit - chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) + //chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) }() // The Identity must exist prior to issuance. if !chain.Identity.IsPopulated() || e.Timestamp.Before(chain.Identity.Timestamp) { @@ -141,7 +141,7 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { rollback := sqlitex.Save(chain.Conn) chainCopy := *chain return func(txErr, err *error) { - e := tx.FactomEntry() + //e := tx.FactomEntry() if *err != nil || *txErr != nil { rollback(&alwaysRollbackErr) // Reset chain on error @@ -149,17 +149,17 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { if *err != nil { return } - chain.Log.Debugf("Entry{%v}: invalid %v Transaction: %v", - e.Hash, chain.Type, *txErr) + //chain.Log.Debugf("Entry{%v}: invalid %v Transaction: %v", + // e.Hash, chain.Type, *txErr) return } rollback(err) - var cbStr string - if tx.IsCoinbase() { - cbStr = "Coinbase " - } - chain.Log.Debugf("Valid %v %vTransaction: %v %+v", - chain.Type, cbStr, e.Hash, tx) + //var cbStr string + //if tx.IsCoinbase() { + // cbStr = "Coinbase " + //} + //chain.Log.Debugf("Valid %v %vTransaction: %v %+v", + // chain.Type, cbStr, e.Hash, tx) } } diff --git a/db/chain.go b/db/chain.go index c3903a0..0328a0f 100644 --- a/db/chain.go +++ b/db/chain.go @@ -82,6 +82,7 @@ type Chain struct { func OpenNew(ctx context.Context, dbPath string, dbKeyMR *factom.Bytes32, eb factom.EBlock, networkID factom.NetworkID, identity factom.Identity) (chain Chain, err error) { + fname := eb.ChainID.String() + dbFileExtension path := dbPath + fname @@ -144,6 +145,8 @@ func OpenNew(ctx context.Context, dbPath string, } func Open(ctx context.Context, dbPath, fname string) (chain Chain, err error) { + chain.Log = _log.New("chain", strings.TrimRight(fname, dbFileExtension)) + chain.Log.Info("Opening...") chain.Conn, chain.Pool, err = OpenConnPool(ctx, dbPath+fname) if err != nil { return @@ -153,7 +156,6 @@ func Open(ctx context.Context, dbPath, fname string) (chain Chain, err error) { chain.Close() } }() - chain.Log = _log.New("chain", strings.TrimRight(fname, dbFileExtension)) chain.DBFile = fname err = chain.loadMetadata() @@ -184,7 +186,6 @@ func OpenAll(ctx context.Context, dbPath string) (chains []Chain, err error) { if err != nil { continue } - log.Debugf("Loading chain: %v", chainID) chain, err := Open(ctx, dbPath, fname) if err != nil { return nil, err diff --git a/db/validate.go b/db/validate.go index e24ae2a..0005aee 100644 --- a/db/validate.go +++ b/db/validate.go @@ -45,8 +45,8 @@ func init() { // // This does not validate the validity of the saved DBlock KeyMRs. func (chain Chain) Validate() (err error) { - chain.Log.Debug("Validating database...") // Validate ChainID... + chain.Log.Info("Validating...") read := chain.Pool.Get(nil) defer chain.Pool.Put(read) write := chain.Conn diff --git a/engine/chain.go b/engine/chain.go index 9744688..b718da4 100644 --- a/engine/chain.go +++ b/engine/chain.go @@ -23,7 +23,6 @@ package engine import ( - "bytes" "context" "fmt" "sync" @@ -36,6 +35,7 @@ import ( "github.com/Factom-Asset-Tokens/fatd/db" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" + _log "github.com/Factom-Asset-Tokens/fatd/log" ) type Chain struct { @@ -64,29 +64,12 @@ func (chain Chain) String() string { func OpenNew(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) (chain Chain, err error) { - if err := eb.Get(ctx, c); err != nil { - return chain, fmt.Errorf("%#v.Get(): %w", eb, err) - } - // Load first entry of new chain. - first := &eb.Entries[0] - if err := first.Get(ctx, c); err != nil { - return chain, fmt.Errorf("%#v.Get(): %w", first, err) - } - if !eb.IsFirst() { - return - } - - // Ignore chains with NameIDs that don't match the fat pattern. - nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { - return - } chain.RWMutex = new(sync.RWMutex) var identity factom.Identity identity.ChainID = new(factom.Bytes32) - _, *identity.ChainID = fat.TokenIssuer(nameIDs) + _, *identity.ChainID = fat.TokenIssuer(eb.Entries[0].ExtIDs) if err = identity.Get(ctx, c); err != nil { // A jsonrpc2.Error indicates that the identity chain // doesn't yet exist, which we tolerate. @@ -115,23 +98,43 @@ func OpenNew(ctx context.Context, c *factom.Client, func OpenNewByChainID(ctx context.Context, c *factom.Client, chainID *factom.Bytes32) (chain Chain, err error) { + + log := _log.New("chain", chainID) + log.Infof("Syncing new...") + eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(ctx, c) if err != nil { err = fmt.Errorf("factom.EBlock.GetPrevAll(): %w", err) return } - first := eblocks[len(eblocks)-1] + firstEB := eblocks[len(eblocks)-1] // Get DBlock Timestamp and KeyMR var dblock factom.DBlock - dblock.Height = first.Height + dblock.Height = firstEB.Height if err = dblock.Get(ctx, c); err != nil { err = fmt.Errorf("factom.DBlock.Get(): %w", err) return } - first.SetTimestamp(dblock.Timestamp) + firstEB.SetTimestamp(dblock.Timestamp) + if err = firstEB.Get(ctx, c); err != nil { + err = fmt.Errorf("%#v.Get(): %w", firstEB, err) + return + } + // Load first entry of new chain. + first := &firstEB.Entries[0] + if err = first.Get(ctx, c); err != nil { + err = fmt.Errorf("%#v.Get(): %w", first, err) + return + } - chain, err = OpenNew(ctx, c, dblock.KeyMR, first) + nameIDs := first.ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + err = fmt.Errorf("not a valid FAT chain: %v", chainID) + return + } + + chain, err = OpenNew(ctx, c, dblock.KeyMR, firstEB) if err != nil { return } @@ -140,10 +143,6 @@ func OpenNewByChainID(ctx context.Context, chain.Close() } }() - if chain.IsUnknown() { - err = fmt.Errorf("not a valid FAT chain: %v", chainID) - return - } // We already applied the first EBlock. Sync the remaining. err = chain.SyncEBlocks(ctx, c, eblocks[:len(eblocks)-1]) @@ -151,6 +150,7 @@ func OpenNewByChainID(ctx context.Context, } func (chain *Chain) Sync(ctx context.Context, c *factom.Client) error { + chain.Log.Infof("Syncing...") eblocks, err := factom.EBlock{ChainID: chain.ID}. GetPrevUpTo(ctx, c, *chain.Head.KeyMR) if err != nil { @@ -159,7 +159,8 @@ func (chain *Chain) Sync(ctx context.Context, c *factom.Client) error { return chain.SyncEBlocks(ctx, c, eblocks) } -func (chain *Chain) SyncEBlocks(ctx context.Context, c *factom.Client, ebs []factom.EBlock) error { +func (chain *Chain) SyncEBlocks( + ctx context.Context, c *factom.Client, ebs []factom.EBlock) error { for i := range ebs { eb := ebs[len(ebs)-1-i] // Earliest EBlock first. @@ -175,6 +176,7 @@ func (chain *Chain) SyncEBlocks(ctx context.Context, c *factom.Client, ebs []fac return err } } + chain.Log.Infof("Synced.") return nil } @@ -207,32 +209,3 @@ func (chain *Chain) conflictFn( chain.Log.Errorf("ChangesetApply Conflict: %v", cType) return sqlite.SQLITE_CHANGESET_ABORT } -func (chain *Chain) revertPending() error { - // We must clear the interrupt to prevent from panicking or being - // interrupted while reverting. - oldDone := chain.SetInterrupt(nil) - defer func() { - // Always clean up our session and snapshots. - chain.Pending.EndSnapshotRead() - chain.Pending.Session.Delete() - chain.Pending.Session = nil - chain.Pending.OfficialSnapshot.Free() - chain.Pending.OfficialSnapshot = nil - chain.SetInterrupt(oldDone) - }() - // Revert all of the pending transactions by applying the inverse of - // the changeset tracked by the session. - var changeset bytes.Buffer - if err := chain.Pending.Session.Changeset(&changeset); err != nil { - return fmt.Errorf("chain.Pending.Session.Changeset(): %w", err) - } - inverse := bytes.NewBuffer(make([]byte, 0, changeset.Len())) - if err := sqlite.ChangesetInvert(inverse, &changeset); err != nil { - return fmt.Errorf("sqlite.ChangesetInvert(): %w", err) - } - if err := chain.Conn.ChangesetApply(inverse, nil, chain.conflictFn); err != nil { - return fmt.Errorf("chain.Conn.ChangesetApply(): %w", err) - - } - return nil -} diff --git a/engine/chainmap.go b/engine/chainmap.go index 9297d53..3d9b9ef 100644 --- a/engine/chainmap.go +++ b/engine/chainmap.go @@ -38,7 +38,7 @@ var ( factom.Bytes32{31: 0x0a}: Chain{ChainStatus: ChainStatusIgnored}, factom.Bytes32{31: 0x0c}: Chain{ChainStatus: ChainStatusIgnored}, factom.Bytes32{31: 0x0f}: Chain{ChainStatus: ChainStatusIgnored}, - }, RWMutex: &sync.RWMutex{}} + }, RWMutex: new(sync.RWMutex)} ) type ChainMap struct { @@ -49,8 +49,8 @@ type ChainMap struct { } func (cm *ChainMap) set(id *factom.Bytes32, chain Chain, prevStatus ChainStatus) { - defer cm.Unlock() cm.Lock() + defer cm.Unlock() cm.m[*id] = chain if chain.ChainStatus != prevStatus { switch chain.ChainStatus { @@ -65,32 +65,31 @@ func (cm *ChainMap) set(id *factom.Bytes32, chain Chain, prevStatus ChainStatus) } } -func (cm ChainMap) ignore(id *factom.Bytes32) { +func (cm *ChainMap) ignore(id *factom.Bytes32) { cm.set(id, Chain{ChainStatus: ChainStatusIgnored}, ChainStatusIgnored) } -func (cm ChainMap) Get(id *factom.Bytes32) Chain { - defer cm.RUnlock() +func (cm *ChainMap) Get(id *factom.Bytes32) Chain { cm.RLock() - chain := cm.m[*id] - return chain + defer cm.RUnlock() + return cm.m[*id] } -func (cm ChainMap) GetIssued() []*factom.Bytes32 { - defer cm.RUnlock() +func (cm *ChainMap) GetIssued() []*factom.Bytes32 { cm.RLock() + defer cm.RUnlock() return cm.issuedIDs } -func (cm ChainMap) GetTracked() []*factom.Bytes32 { - defer cm.RUnlock() +func (cm *ChainMap) GetTracked() []*factom.Bytes32 { cm.RLock() + defer cm.RUnlock() return cm.trackedIDs } -func (cm ChainMap) setSync(height uint32, dbKeyMR *factom.Bytes32) error { - defer cm.Unlock() +func (cm *ChainMap) setSync(height uint32, dbKeyMR *factom.Bytes32) error { cm.Lock() + defer cm.Unlock() for _, chain := range cm.m { if !chain.IsTracked() { continue @@ -104,9 +103,9 @@ func (cm ChainMap) setSync(height uint32, dbKeyMR *factom.Bytes32) error { return nil } -func (cm ChainMap) Close() { - defer cm.Unlock() +func (cm *ChainMap) Close() { cm.Lock() + defer cm.Unlock() for _, chain := range cm.m { if chain.IsTracked() { // Rollback any pending entries on the chain. @@ -138,7 +137,8 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { } } }() - + Chains.Lock() + defer Chains.Unlock() // Set whitelisted chains to Tracked. for _, chainID := range flag.Whitelist { Chains.m[chainID] = Chain{ChainStatus: ChainStatusTracked} @@ -196,9 +196,10 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { // Open any whitelisted chains that do not already have databases. for id, chain := range Chains.m { - if !chain.IsTracked() || chain.Chain.Conn != nil { + if !(chain.IsTracked() && chain.Chain.Conn == nil) { continue } + id := id var chain Chain chain, err = OpenNewByChainID(ctx, c, &id) if err != nil { diff --git a/engine/engine.go b/engine/engine.go index 9f813fd..78ae5f8 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -243,8 +243,7 @@ func engine(ctx context.Context, done chan struct{}) { for { if !synced && syncHeight == factomHeight { synced = true - log.Debugf("Synced to block %v...", syncHeight) - log.Infof("Synced.") + log.Infof("Synced to block %v.", syncHeight) } // Process all new DBlocks sequentially. @@ -332,13 +331,15 @@ func engine(ctx context.Context, done chan struct{}) { // this ChainID. for j = i + 1; j < len(pe); j++ { chainID := pe[j].ChainID - if chainID == nil || *chainID != *e.ChainID { + if chainID == nil || + *chainID != *e.ChainID { break } } // Process all pending entries for this // chain. - if err := ProcessPending(ctx, pe[i:j]...); err != nil { + if err := ProcessPending( + ctx, pe[i:j]...); err != nil { runIfNotDone(ctx, func() { log.Error(err) }) @@ -355,7 +356,8 @@ func engine(ctx context.Context, done chan struct{}) { } } - // Check the Factom blockchain height but log and retry if this request fails. + // Check the Factom blockchain height but log and retry if this + // request fails. if err := updateFactomHeight(ctx); err != nil { log.Error(err) if flag.FactomScanRetries > -1 && diff --git a/engine/process.go b/engine/process.go index 21e4866..699a2cd 100644 --- a/engine/process.go +++ b/engine/process.go @@ -23,11 +23,16 @@ package engine import ( + "bytes" "context" + "fmt" "time" + "crawshaw.io/sqlite" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/flag" ) @@ -41,6 +46,26 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err return nil } if chain.IsUnknown() { + if err := eb.Get(ctx, c); err != nil { + return fmt.Errorf("%#v.Get(): %w", eb, err) + } + if !eb.IsFirst() { + Chains.ignore(eb.ChainID) + return nil + } + + // Load first entry of new chain. + first := &eb.Entries[0] + if err := first.Get(ctx, c); err != nil { + return fmt.Errorf("%#v.Get(): %w", first, err) + } + // Ignore chains with NameIDs that don't match the fat pattern. + nameIDs := first.ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + Chains.ignore(eb.ChainID) + return nil + } + // Attempt to open a new chain. var err error chain, err = OpenNew(ctx, c, dbKeyMR, eb) @@ -48,11 +73,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err return err } - // Ignore if the chain was not opened. - if chain.IsUnknown() { - Chains.ignore(eb.ChainID) - return nil - } + log.Infof("Tracking new FAT chain: %v", chain.ID) // Fully sync the chain, so that it is up to date immediately. if err := chain.Sync(ctx, c); err != nil { @@ -73,7 +94,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err defer chain.Unlock() // Rollback any pending entries on the chain. - if chain.Pending.OfficialSnapshot != nil { + if chain.Pending.Entries != nil { chain.Log.Debug("Cleaning up pending state...") // Load any cached entries that are pending and remove them // from the cache. @@ -89,16 +110,6 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err // Use official Timestamp established by EBlock. cachedE.Timestamp = e.Timestamp *e = cachedE - - // Delete the entry from the pending cache, rather than - // nil the entire map, because this allows the cache - // map size for various chains to grow according to how - // active they are. But this is not optimal for chains - // with only very occasional bursts of pending entries. - // - // A mechanism to eventually free rarely used pending - // entry maps would be a good improvement later. - delete(chain.Pending.Entries, *e.Hash) } err := chain.revertPending() @@ -136,60 +147,13 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { chain.Lock() defer chain.Unlock() - // Initialize Pending if there is no snapshot yet. - if chain.Pending.OfficialSnapshot == nil { - chain.Log.Debug("Initializing pending...") - // Create the cache if it does not exist. - if chain.Pending.Entries == nil { - chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) - } - - // Take a snapshot of the official state and copy the current - // official Chain. - s, err := chain.Conn.CreateSnapshot("") - if err != nil { - return err - } - - // SQLite does not guanratee that snapshots will remain - // readable over time due to automatic WAL checkpoints. Thus we - // must keep a read transaction open on the snapshot to prevent - // the WAL autocheckpoints from going past the snapshot. - readConn := chain.Pool.Get(ctx) - endRead, err := readConn.StartSnapshotRead(s) - if err != nil { - return err - } - chain.Pending.EndSnapshotRead = func() { - // We must clear the interrupt to prevent from - // panicking. - readConn.SetInterrupt(nil) - endRead() - chain.Pool.Put(readConn) - } - chain.Pending.OfficialSnapshot = s - chain.Pending.OfficialChain = chain.Chain - - // Start a new session so we can track all changes and later - // rollback all pending entries. - session, err := chain.Conn.CreateSession("") - if err != nil { - return err - } - if err := session.Attach(""); err != nil { + // Initialize Pending if Entries is not yet populated. + if chain.Pending.Entries == nil { + if err := chain.initPending(ctx); err != nil { return err } - chain.Pending.Session = session - - // There is a chance the Identity is populated now but wasn't - // before, so update it now. - if err := chain.Identity.Get(ctx, c); err != nil { - // A jsonrpc2.Error indicates that the identity chain - // doesn't yet exist, which we tolerate. - if _, ok := err.(jsonrpc2.Error); !ok { - return err - } - } + // Ensure the chain is saved back into the map. + Chains.set(chain.ID, chain, chain.ChainStatus) } // startLenEntries tracks the initial size of our cache so we can @@ -225,10 +189,130 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { return nil } + chain.Log.Debugf("Applied %v new pending entries.", + len(chain.Pending.Entries)-startLenEntries) + // Save the chain back into the map. Chains.set(chain.ID, chain, chain.ChainStatus) return nil } +func (chain *Chain) initPending(ctx context.Context) (err error) { + chain.Log.Debug("Initializing pending...") + + chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) + chain.Pending.OfficialChain = chain.Chain + + defer func() { + if err != nil { + chain.Pending.Entries = nil + chain.Pending.OfficialChain = db.Chain{} + } + }() + + // Take a snapshot of the official state and copy the current + // official Chain. + s, err := chain.Conn.CreateSnapshot("") + if err != nil { + return + } + chain.Pending.OfficialSnapshot = s + defer func() { + if err != nil { + chain.Pending.OfficialSnapshot.Free() + chain.Pending.OfficialSnapshot = nil + } + }() + + readConn := chain.Pool.Get(ctx) + defer func() { + if err != nil { + chain.Pool.Put(readConn) + } + }() + + // SQLite does not guanratee that snapshots will remain + // readable over time due to automatic WAL checkpoints. Thus we + // must keep a read transaction open on the snapshot to prevent + // the WAL autocheckpoints from going past the snapshot. + endRead, err := readConn.StartSnapshotRead(s) + if err != nil { + return err + } + defer func() { + if err != nil { + endRead() + } + }() + + // Start a new session so we can track all changes and later + // rollback all pending entries. + session, err := chain.Conn.CreateSession("") + if err != nil { + return err + } + chain.Pending.Session = session + chain.Pending.EndSnapshotRead = func() { + // We must clear the interrupt to prevent from + // panicking. + readConn.SetInterrupt(nil) + endRead() + chain.Pool.Put(readConn) + } + defer func() { + if err != nil { + session.Delete() + chain.Pending.Session = nil + chain.Pending.EndSnapshotRead = nil + } + }() + + if err := chain.Pending.Session.Attach(""); err != nil { + return err + } + + // There is a chance the Identity is populated now but wasn't + // before, so update it now. + if err := chain.Identity.Get(ctx, c); err != nil { + // A jsonrpc2.Error indicates that the identity chain + // doesn't yet exist, which we tolerate. + if _, ok := err.(jsonrpc2.Error); !ok { + return err + } + } + return nil +} +func (chain *Chain) revertPending() error { + chain.Pending.Entries = nil + // We must clear the interrupt to prevent from panicking or being + // interrupted while reverting. + oldDone := chain.Conn.SetInterrupt(nil) + defer func() { + // Always clean up our session and snapshots. + chain.Pending.EndSnapshotRead() + chain.Pending.EndSnapshotRead = nil + + chain.Pending.Session.Delete() + chain.Pending.Session = nil + chain.Pending.OfficialSnapshot.Free() + chain.Pending.OfficialSnapshot = nil + chain.Conn.SetInterrupt(oldDone) + }() + // Revert all of the pending transactions by applying the inverse of + // the changeset tracked by the session. + var changeset bytes.Buffer + if err := chain.Pending.Session.Changeset(&changeset); err != nil { + return fmt.Errorf("chain.Pending.Session.Changeset(): %w", err) + } + inverse := bytes.NewBuffer(make([]byte, 0, changeset.Len())) + if err := sqlite.ChangesetInvert(inverse, &changeset); err != nil { + return fmt.Errorf("sqlite.ChangesetInvert(): %w", err) + } + if err := chain.Conn.ChangesetApply(inverse, nil, chain.conflictFn); err != nil { + return fmt.Errorf("chain.Conn.ChangesetApply(): %w", err) + + } + return nil +} // Get returns a threadsafe connection to the database, and a function to // release the connection back to the pool. If pending is true, the chain will @@ -243,7 +327,10 @@ func (chain *Chain) Get(ctx context.Context, pending bool) func() { // If pending or if there is no pending state, then use the chain as // is, and just return a function that returns the conn to the pool. if pending || chain.Pending.OfficialSnapshot == nil { - return func() { chain.Pool.Put(conn); chain.RUnlock() } + return func() { + chain.Pool.Put(conn) + chain.RUnlock() + } } // Use the official chain state with the conn from the Pool. diff --git a/go.mod b/go.mod index bf86412..84c770d 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb - github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 + github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d - github.com/Factom-Asset-Tokens/factom v0.0.0-20191006004400-d347b69e7a7b + github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index 8897afb..a07ae34 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBn github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmKY1f0AITEyfhLaH9zpqIwLgVIKLE+BBCY2B2gys= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7 h1:ZCYtk8/0h7AD2CxrdHLigS+ck6Exl2pRiuJFShetmrQ= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23 h1:rgBx93rHyTKWrAYaRfaD6DZcc6ezXflPtnxRlsG1el4= @@ -15,8 +17,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191006004400-d347b69e7a7b h1:6ux+qEf0rxQVBfETVgGimR+QojbcGnXDUgCe+B/MKHM= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191006004400-d347b69e7a7b/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d h1:DTgnFMb30d+3Q38c+UAfgZ/ePHhaDZFpg1jeAZbO74c= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/srv/methods.go b/srv/methods.go index fdf8d16..55079d9 100644 --- a/srv/methods.go +++ b/srv/methods.go @@ -670,7 +670,7 @@ func validate(ctx context.Context, if !chain.IsIssued() { return nil, nil, ErrorTokenNotFound } - ctx, cancel := context.WithTimeout(ctx, time.Second) + ctx, cancel := context.WithTimeout(ctx, 2*time.Second) put := chain.Get(ctx, params.HasIncludePending()) return &chain, func() { cancel(); put() }, nil } From 8f218d6a1b1b0d67662c9d6a3ff97b2f46f0fe3c Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 10 Oct 2019 19:57:18 -0800 Subject: [PATCH 087/124] feat(flag,engine): Add -factomscaninterval to control scan frequency for blocks and pending entries close #38 --- engine/engine.go | 2 +- flag/flag.go | 50 ++++++++++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 78ae5f8..81acec1 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -237,7 +237,7 @@ func engine(ctx context.Context, done chan struct{}) { var retries int64 // scanTicker kicks off a new scan. - scanTicker := time.NewTicker(scanInterval) + scanTicker := time.NewTicker(flag.FactomScanInterval) // Factom Blockchain Scan Loop for { diff --git a/flag/flag.go b/flag/flag.go index 66b4763..bcef025 100644 --- a/flag/flag.go +++ b/flag/flag.go @@ -43,10 +43,11 @@ const envNamePrefix = "FATD_" var ( envNames = map[string]string{ - "startscanheight": "START_SCAN_HEIGHT", - "factomscanretries": "FACTOM_SCAN_RETRIES", - "debug": "DEBUG", - "disablepending": "DISABLE_PENDING", + "startscanheight": "START_SCAN_HEIGHT", + "factomscanretries": "FACTOM_SCAN_RETRIES", + "factomscaninterval": "FACTOM_SCAN_INTERVAL", + "debug": "DEBUG", + "disablepending": "DISABLE_PENDING", "dbpath": "DB_PATH", @@ -81,10 +82,11 @@ var ( "skipdbvalidation": "SKIP_DB_VALIDATION", } defaults = map[string]interface{}{ - "startscanheight": uint64(0), - "factomscanretries": int64(0), - "debug": false, - "disablepending": false, + "startscanheight": uint64(0), + "factomscanretries": int64(0), + "factomscaninterval": 15 * time.Second, + "debug": false, + "disablepending": false, "dbpath": "./fatd.db", @@ -115,10 +117,11 @@ var ( "skipdbvalidation": false, } descriptions = map[string]string{ - "startscanheight": "Block height to start scanning for deposits on startup", - "factomscanretries": "Number of times to consecutively retry fetching the latest height before exiting, use -1 for unlimited", - "debug": "Log debug messages", - "disablepending": "Do not scan for pending txs, reducing memory usage", + "startscanheight": "Block height to start scanning for deposits on startup", + "factomscanretries": "Number of times to consecutively retry fetching the latest height before exiting, use -1 for unlimited", + "factomscaninterval": "Scan interval for new blocks or pending entries", + "debug": "Log debug messages", + "disablepending": "Do not scan for pending txs, reducing memory usage", "dbpath": "Path to the folder containing all database files", @@ -152,10 +155,11 @@ var ( "skipdbvalidation": "Skip the full validation check of all chain databases", } flags = complete.Flags{ - "-startscanheight": complete.PredictAnything, - "-factomscanretries": complete.PredictAnything, - "-debug": complete.PredictNothing, - "-disablepending": complete.PredictNothing, + "-startscanheight": complete.PredictAnything, + "-factomscanretries": complete.PredictAnything, + "-factomscaninterval": complete.PredictAnything, + "-debug": complete.PredictNothing, + "-disablepending": complete.PredictNothing, "-dbpath": complete.PredictFiles("*"), @@ -200,11 +204,12 @@ var ( "-skipdbvalidation": complete.PredictNothing, } - startScanHeight uint64 // We parse the flag as unsigned. - StartScanHeight int32 = -1 // We work with the signed value. - LogDebug bool - DisablePending bool - FactomScanRetries int64 = -1 + startScanHeight uint64 // We parse the flag as unsigned. + StartScanHeight int32 = -1 // We work with the signed value. + FactomScanInterval time.Duration + LogDebug bool + DisablePending bool + FactomScanRetries int64 = -1 EsAdr factom.EsAddress ECAdr factom.ECAddress @@ -236,6 +241,7 @@ var ( func init() { flagVar(&startScanHeight, "startscanheight") flagVar(&FactomScanRetries, "factomscanretries") + flagVar(&FactomScanInterval, "factomscaninterval") flagVar(&LogDebug, "debug") flagVar(&DisablePending, "disablepending") @@ -289,6 +295,7 @@ func Parse() { // specified on the command line. loadFromEnv(&startScanHeight, "startscanheight") loadFromEnv(&FactomScanRetries, "factomscanretries") + loadFromEnv(&FactomScanInterval, "factomscaninterval") loadFromEnv(&LogDebug, "debug") loadFromEnv(&DisablePending, "disablepending") @@ -340,6 +347,7 @@ func Validate() { log.Debugf("-apiaddress %#v", APIAddress) log.Debugf("-startscanheight %v ", StartScanHeight) log.Debugf("-factomscanretries %v ", FactomScanRetries) + log.Debugf("-startscaninterval %v ", FactomScanInterval) debugPrintln() log.Debugf("-networkid %v", NetworkID) From 2056f5ce53624af4c4698db9f01048b439306e1d Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 11 Oct 2019 11:51:57 -0800 Subject: [PATCH 088/124] feat(fat0): Allow 0 amounts and overlapping inputs and outputs --- fat/fat0/addressamountmap.go | 11 ++------- fat/fat0/transaction.go | 4 ---- fat/fat0/transaction_test.go | 44 ++++-------------------------------- fat/issuance_test.go | 4 ++-- 4 files changed, 9 insertions(+), 54 deletions(-) diff --git a/fat/fat0/addressamountmap.go b/fat/fat0/addressamountmap.go index 89d5386..e350857 100644 --- a/fat/fat0/addressamountmap.go +++ b/fat/fat0/addressamountmap.go @@ -37,15 +37,11 @@ type AddressAmountMap map[factom.FAAddress]uint64 // MarshalJSON marshals a list of addresses and amounts used in the inputs or // outputs of a transaction. Addresses with a 0 amount are omitted. func (m AddressAmountMap) MarshalJSON() ([]byte, error) { - if m.Sum() == 0 { + if len(m) == 0 { return nil, fmt.Errorf("empty") } adrStrAmountMap := make(map[string]uint64, len(m)) for adr, amount := range m { - // Omit addresses with 0 amounts. - if amount == 0 { - continue - } adrStrAmountMap[adr.String()] = amount } return json.Marshal(adrStrAmountMap) @@ -65,16 +61,13 @@ func (m *AddressAmountMap) UnmarshalJSON(data []byte) error { return fmt.Errorf("%T: empty", m) } adrJSONLen := len(`"":,`) + adrStrLen - expectedJSONLen := len(`{}`) - len(`,`) + len(adrStrAmountMap)*adrJSONLen + expectedJSONLen := len(`{}`) + len(adrStrAmountMap)*adrJSONLen - len(`,`) *m = make(AddressAmountMap, len(adrStrAmountMap)) var adr factom.FAAddress for adrStr, amount := range adrStrAmountMap { if err := adr.Set(adrStr); err != nil { return fmt.Errorf("%T: %w", m, err) } - if amount == 0 { - return fmt.Errorf("%T: invalid amount (0): %v", m, adr) - } (*m)[adr] = amount expectedJSONLen += jsonlen.Uint64(amount) } diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go index 8791414..cc41719 100644 --- a/fat/fat0/transaction.go +++ b/fat/fat0/transaction.go @@ -147,10 +147,6 @@ func (t Transaction) ValidData() error { if t.IsCoinbase() && len(t.Inputs) != 1 { return fmt.Errorf("invalid coinbase transaction") } - // Ensure that no address exists in both the Inputs and Outputs. - if err := t.Inputs.NoAddressIntersection(t.Outputs); err != nil { - return err - } return nil } diff --git a/fat/fat0/transaction_test.go b/fat/fat0/transaction_test.go index 9435d3c..7d07d32 100644 --- a/fat/fat0/transaction_test.go +++ b/fat/fat0/transaction_test.go @@ -82,14 +82,6 @@ var transactionTests = []struct { Name: "invalid JSON (invalid outputs type)", Error: "*fat0.Transaction.Outputs: json: cannot unmarshal array into Go value of type map[string]uint64", Tx: invalidField("outputs"), -}, { - Name: "invalid JSON (invalid inputs, zero amount)", - Error: "*fat0.Transaction.Inputs: *fat0.AddressAmountMap: invalid amount (0): ", - Tx: func() *Transaction { - in := inputs() - in[inputAddresses[0].FAAddress().String()] = 0 - return setFieldTransaction("inputs", in) - }(), }, { Name: "invalid JSON (invalid inputs, duplicate)", Error: "*fat0.Transaction.Inputs: *fat0.AddressAmountMap: unexpected JSON length", @@ -144,32 +136,6 @@ var transactionTests = []struct { m["outputs"] = out return transaction(marshal(m)) }(), -}, { - Name: "invalid data (coinbase, coinbase outputs)", - Error: "*fat0.Transaction: duplicate address: ", - IssuerKey: issuerKey, - Tx: func() *Transaction { - m := validCoinbaseTxEntryContentMap() - in := coinbaseInputs() - out := coinbaseOutputs() - in[fat.Coinbase().String()]++ - out[fat.Coinbase().String()]++ - m["inputs"] = in - m["outputs"] = out - return transaction(marshal(m)) - }(), -}, { - Name: "invalid data (inputs outputs overlap)", - Error: "*fat0.Transaction: duplicate address: ", - Tx: func() *Transaction { - m := validTxEntryContentMap() - in := inputs() - in[outputAddresses[0].FAAddress().String()] = - in[inputAddresses[0].FAAddress().String()] - delete(in, inputAddresses[0].FAAddress().String()) - m["inputs"] = in - return transaction(marshal(m)) - }(), }, { Name: "invalid ExtIDs (timestamp)", Error: "timestamp salt expired", @@ -233,7 +199,7 @@ var ( coinbaseInputAmounts = []uint64{110} coinbaseOutputAmounts = []uint64{90, 20} - tokenChainID = fat.ChainID("test", identityChainID) + tokenChainID = fat.ComputeChainID("test", identityChainID) identityChainID = factom.NewBytes32(validIdentityChainID()) ) @@ -294,21 +260,21 @@ func validCoinbaseTxEntryContentMap() map[string]interface{} { // inputs/outputs func inputs() map[string]uint64 { - var inputs map[string]uint64 + inputs := make(map[string]uint64) for i := range inputAddresses { inputs[inputAddresses[i].FAAddress().String()] = inputAmounts[i] } return inputs } func outputs() map[string]uint64 { - var outputs map[string]uint64 + outputs := make(map[string]uint64) for i := range outputAddresses { outputs[outputAddresses[i].FAAddress().String()] = outputAmounts[i] } return outputs } func coinbaseInputs() map[string]uint64 { - var inputs map[string]uint64 + inputs := make(map[string]uint64) for i := range coinbaseInputAddresses { inputs[coinbaseInputAddresses[i].FAAddress().String()] = coinbaseInputAmounts[i] @@ -316,7 +282,7 @@ func coinbaseInputs() map[string]uint64 { return inputs } func coinbaseOutputs() map[string]uint64 { - var outputs map[string]uint64 + outputs := make(map[string]uint64) for i := range coinbaseOutputAddresses { outputs[coinbaseOutputAddresses[i].FAAddress().String()] = coinbaseOutputAmounts[i] diff --git a/fat/issuance_test.go b/fat/issuance_test.go index 3c7772e..514545e 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -61,7 +61,7 @@ var ( func TestChainID(t *testing.T) { assert.Equal(t, "b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb", - ChainID("test", identityChainID).String()) + ComputeChainID("test", identityChainID).String()) } var validTokenNameIDsTests = []struct { @@ -301,7 +301,7 @@ var issuanceMarshalEntryTests = []struct { }(), }, { Name: "invalid data", - Error: `json: error calling MarshalJSON for type *fat.Issuance: invalid "type": FAT-1000`, + Error: `json: error calling MarshalJSON for type *fat.Issuance: invalid "type": invalid fat.Type: 1000`, Issuance: func() Issuance { i := newIssuance() i.Type = 1000 From 3f71f7932fca7a26f49017933daadbd9b370cd7a Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 11 Oct 2019 12:14:47 -0800 Subject: [PATCH 089/124] refactor(db): Fix generate and test --- db/chain.go | 2 +- db/chain_test.go | 3 +- db/gentestdb.go | 26 ++++++++++-------- ...bad3f56169f94966341018b1950542f3dd.sqlite3 | Bin 225280 -> 241664 bytes ...b10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 | Bin 114688 -> 151552 bytes 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/db/chain.go b/db/chain.go index 0328a0f..3e9ae4d 100644 --- a/db/chain.go +++ b/db/chain.go @@ -95,7 +95,7 @@ func OpenNew(ctx context.Context, dbPath string, // Ensure that the database file doesn't already exist. _, err = os.Stat(path) if err == nil { - err = fmt.Errorf("already exists: %w", path) + err = fmt.Errorf("already exists: %v", path) return } if !os.IsNotExist(err) { // Any other error is unexpected. diff --git a/db/chain_test.go b/db/chain_test.go index c03cc08..064f8e0 100644 --- a/db/chain_test.go +++ b/db/chain_test.go @@ -23,6 +23,7 @@ package db import ( + "context" "testing" "github.com/Factom-Asset-Tokens/fatd/flag" @@ -33,7 +34,7 @@ import ( func TestChainValidate(t *testing.T) { require := require.New(t) flag.LogDebug = true - chains, err := OpenAll("./test-fatd.db/") + chains, err := OpenAll(context.Background(), "./test-fatd.db/") require.NoError(err, "OpenAll()") require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) diff --git a/db/gentestdb.go b/db/gentestdb.go index 665be56..db07b5b 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -25,6 +25,7 @@ package main import ( + "context" "flag" "fmt" "log" @@ -57,21 +58,21 @@ func main() { log.SetPrefix(fmt.Sprintf("ChainID: %v ", chainID.String())) - eblocks, err := EBlock{ChainID: chainID}.GetPrevAll(c) + eblocks, err := EBlock{ChainID: chainID}.GetPrevAll(context.Background(), c) if err != nil { log.Fatal(err) } first := eblocks[len(eblocks)-1] var dblock DBlock - dblock.Header.Height = first.Height - if err := dblock.Get(c); err != nil { + dblock.Height = first.Height + if err := dblock.Get(context.Background(), c); err != nil { log.Fatal(err) } - timestamp := dblock.Header.Timestamp + timestamp := dblock.Timestamp for i := range first.Entries { e := &first.Entries[i] - if err := e.Get(c); err != nil { + if err := e.Get(context.Background(), c); err != nil { log.Fatal(err) } e.Timestamp = timestamp.Add(e.Timestamp.Sub(first.Timestamp)) @@ -84,13 +85,14 @@ func main() { log.Fatalf("invalid token chain") } _, identityChainID := fat.TokenIssuer(nameIDs) - identity := NewIdentity(identityChainID) - if err := identity.Get(c); err != nil { + identity := NewIdentity(&identityChainID) + if err := identity.Get(context.Background(), c); err != nil { log.Fatal(err) } // We don't need the actual dbKeyMR - chain, err := db.OpenNew(fflag.DBPath, dblock.KeyMR, first, MainnetID(), identity) + chain, err := db.OpenNew(context.Background(), + fflag.DBPath, dblock.KeyMR, first, MainnetID(), identity) if err != nil { log.Println(err) return @@ -100,15 +102,15 @@ func main() { eblocks = eblocks[:len(eblocks)-1] // skip first eblock for i := range eblocks { eb := eblocks[len(eblocks)-i-1] - if err := eb.GetEntries(c); err != nil { + if err := eb.GetEntries(context.Background(), c); err != nil { log.Fatal(err) } var dblock DBlock - dblock.Header.Height = eb.Height - if err := dblock.Get(c); err != nil { + dblock.Height = eb.Height + if err := dblock.Get(context.Background(), c); err != nil { log.Fatal(err) } - eb.SetTimestamp(dblock.Header.Timestamp) + eb.SetTimestamp(dblock.Timestamp) if err := chain.Apply(dblock.KeyMR, eb); err != nil { log.Fatal(err) diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 index 443ed6d0b681d22220b2454d52b93afa43caf2df..29beba2b5afcf6ba7f7107e9d21562d77a04e973 100644 GIT binary patch literal 241664 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

  • l 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

    XZ%rfQ0zQS)BqqY4l^nN==K>F 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 diff --git a/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 b/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 index 7416cd4fe28d6df97249a7a4d39cb02c5ae7546e..5c7f87ab6b560da43e357deb54dd4bbd2172d2fb 100644 GIT binary patch delta 34901 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 delta 920 zcmah{TS!z<6usv@=FZGLXKxj?)Y0ywO+_LTy~VJSEJ>w4q!0VZP?E~B7exL{V3C%M zc7hqC8vBVLNb_1(2(l+iLLAd{p#5?n|6txcqgKy|V{XVogwTy>;o*S7((k115fN#_V3PT{1T)JWcAvSU0`JafUh!H;XXzO%$i9{vh^?Sa8kzfmL^ z!^SsSY!n&SVUJNmYawBr!t=Np!ZefJWM^@V?PgP8vR7zhF8vN0=?!=deJq=8zy|t) zwg?MfyjD@uV>JUuB@Y~x;34*6EAGNVOo5MZ5snJYYT(J?h5XvNO1?T=>DE>TG*zmQ z`N5c}ralO$d^8-80}5{qFVy7O$(@+#W+7obP~qlpV`ipvga`U09K!~9?f4Ns6L=o_ z1b-*+zROXEPSc#kc|2@tv)S`=VUypERVqPf=6Y9+|jco5> zPl~=m#n#2y{wj|dxt#7-TAJ#*cfuz@qj$v^BWP^ B9B=>t From 578334e501c694c77c2be6b6c08365692af57886 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 11 Oct 2019 15:18:36 -0800 Subject: [PATCH 090/124] refactor(internal): Move engine, flag, log, db, & srv packages to internal In order to prepare for a v1.0 release, reduce the public API surface by moving packages into the internal folder. --- {srv => api}/client.go | 2 +- {srv => api}/errors.go | 2 +- {srv => api}/params.go | 2 +- api/results.go | 104 ++++++++++++ cli/{cmd => }/complete.go | 2 +- cli/{cmd => }/get.go | 2 +- cli/{cmd => }/getbalance.go | 20 +-- cli/{cmd => }/getchains.go | 12 +- cli/{cmd => }/gettransactions.go | 18 +-- cli/{cmd => }/issue.go | 10 +- cli/main.go | 6 +- cli/{cmd => }/predict.go | 6 +- cli/{cmd => }/root.go | 10 +- cli/{cmd => }/transact.go | 16 +- cli/{cmd => }/transactfat0.go | 2 +- cli/{cmd => }/transactfat1.go | 2 +- {db => internal/db}/addresses/addresses.go | 0 {db => internal/db}/addresses/txrelation.go | 0 {db => internal/db}/apply.go | 10 +- {db => internal/db}/chain.go | 10 +- {db => internal/db}/chain_test.go | 2 +- {db => internal/db}/eblocks/eblocks.go | 0 {db => internal/db}/entries/entries.go | 2 +- {db => internal/db}/gen.go | 0 {db => internal/db}/gentestdb.go | 4 +- {db => internal/db}/metadata/metadata.go | 2 +- {db => internal/db}/nftokens/nftokens.go | 2 +- {db => internal/db}/nftokens/txrelation.go | 0 {db => internal/db}/schema.go | 10 +- {db => internal/db}/sqlbuilder/sqlbuilder.go | 0 ...bad3f56169f94966341018b1950542f3dd.sqlite3 | Bin ...b10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 | Bin {db => internal/db}/validate.go | 6 +- {engine => internal/engine}/chain.go | 6 +- {engine => internal/engine}/chainmap.go | 4 +- {engine => internal/engine}/chainstatus.go | 0 .../engine}/chainstatus_test.go | 0 {engine => internal/engine}/engine.go | 4 +- {engine => internal/engine}/process.go | 4 +- {flag => internal/flag}/flag.go | 0 {flag => internal/flag}/list.go | 0 {flag => internal/flag}/predict.go | 0 {log => internal/log}/log.go | 2 +- {srv => internal/srv}/doc.go | 0 {srv => internal/srv}/methods.go | 150 +++++------------- {srv => internal/srv}/srv.go | 4 +- main.go | 8 +- 47 files changed, 239 insertions(+), 207 deletions(-) rename {srv => api}/client.go (99%) rename {srv => api}/errors.go (99%) rename {srv => api}/params.go (99%) create mode 100644 api/results.go rename cli/{cmd => }/complete.go (99%) rename cli/{cmd => }/get.go (99%) rename cli/{cmd => }/getbalance.go (93%) rename cli/{cmd => }/getchains.go (94%) rename cli/{cmd => }/gettransactions.go (94%) rename cli/{cmd => }/issue.go (98%) rename cli/{cmd => }/predict.go (97%) rename cli/{cmd => }/root.go (98%) rename cli/{cmd => }/transact.go (96%) rename cli/{cmd => }/transactfat0.go (99%) rename cli/{cmd => }/transactfat1.go (99%) rename {db => internal/db}/addresses/addresses.go (100%) rename {db => internal/db}/addresses/txrelation.go (100%) rename {db => internal/db}/apply.go (96%) rename {db => internal/db}/chain.go (96%) rename {db => internal/db}/chain_test.go (96%) rename {db => internal/db}/eblocks/eblocks.go (100%) rename {db => internal/db}/entries/entries.go (99%) rename {db => internal/db}/gen.go (100%) rename {db => internal/db}/gentestdb.go (96%) rename {db => internal/db}/metadata/metadata.go (98%) rename {db => internal/db}/nftokens/nftokens.go (99%) rename {db => internal/db}/nftokens/txrelation.go (100%) rename {db => internal/db}/schema.go (89%) rename {db => internal/db}/sqlbuilder/sqlbuilder.go (100%) rename {db => internal/db}/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 (100%) rename {db => internal/db}/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 (100%) rename {db => internal/db}/validate.go (97%) rename {engine => internal/engine}/chain.go (97%) rename {engine => internal/engine}/chainmap.go (98%) rename {engine => internal/engine}/chainstatus.go (100%) rename {engine => internal/engine}/chainstatus_test.go (100%) rename {engine => internal/engine}/engine.go (98%) rename {engine => internal/engine}/process.go (98%) rename {flag => internal/flag}/flag.go (100%) rename {flag => internal/flag}/list.go (100%) rename {flag => internal/flag}/predict.go (100%) rename {log => internal/log}/log.go (96%) rename {srv => internal/srv}/doc.go (100%) rename {srv => internal/srv}/methods.go (79%) rename {srv => internal/srv}/srv.go (96%) diff --git a/srv/client.go b/api/client.go similarity index 99% rename from srv/client.go rename to api/client.go index f9c02da..6814cb5 100644 --- a/srv/client.go +++ b/api/client.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package srv +package api import ( "context" diff --git a/srv/errors.go b/api/errors.go similarity index 99% rename from srv/errors.go rename to api/errors.go index 2bff1bb..0ce4be8 100644 --- a/srv/errors.go +++ b/api/errors.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package srv +package api import jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" diff --git a/srv/params.go b/api/params.go similarity index 99% rename from srv/params.go rename to api/params.go index ced8cae..b29d057 100644 --- a/srv/params.go +++ b/api/params.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package srv +package api import ( "strings" diff --git a/api/results.go b/api/results.go new file mode 100644 index 0000000..50a3861 --- /dev/null +++ b/api/results.go @@ -0,0 +1,104 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package api + +import ( + "encoding/json" + + "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" +) + +const APIVersion = "1" + +type ResultGetIssuance struct { + ParamsToken + Hash *factom.Bytes32 `json:"entryhash"` + Timestamp int64 `json:"timestamp"` + Issuance fat.Issuance `json:"issuance"` +} + +type ResultGetTransaction struct { + Hash *factom.Bytes32 `json:"entryhash"` + Timestamp int64 `json:"timestamp"` + Tx interface{} `json:"data"` + Pending bool `json:"pending,omitempty"` +} + +type ResultGetBalances map[factom.Bytes32]uint64 + +func (r ResultGetBalances) MarshalJSON() ([]byte, error) { + strMap := make(map[string]uint64, len(r)) + for chainID, balance := range r { + strMap[chainID.String()] = balance + } + return json.Marshal(strMap) +} + +func (r *ResultGetBalances) UnmarshalJSON(data []byte) error { + var strMap map[string]uint64 + if err := json.Unmarshal(data, &strMap); err != nil { + return err + } + *r = make(map[factom.Bytes32]uint64, len(strMap)) + var chainID factom.Bytes32 + for str, balance := range strMap { + if err := chainID.Set(str); err != nil { + return err + } + (*r)[chainID] = balance + } + return nil +} + +type ResultGetStats struct { + ParamsToken + Issuance *fat.Issuance + IssuanceHash *factom.Bytes32 + CirculatingSupply uint64 `json:"circulating"` + Burned uint64 `json:"burned"` + Transactions int64 `json:"transactions"` + IssuanceTimestamp int64 `json:"issuancets"` + LastTransactionTimestamp int64 `json:"lasttxts,omitempty"` + NonZeroBalances int64 `json:"nonzerobalances, omitempty"` +} + +type ResultGetNFToken struct { + NFTokenID fat1.NFTokenID `json:"id"` + Owner *factom.FAAddress `json:"owner,omitempty"` + Burned bool `json:"burned,omitempty"` + Metadata json.RawMessage `json:"metadata,omitempty"` + CreationTx *factom.Bytes32 `json:"creationtx"` +} + +type ResultGetDaemonProperties struct { + FatdVersion string `json:"fatdversion"` + APIVersion string `json:"apiversion"` + NetworkID factom.NetworkID `json:"factomnetworkid"` +} + +type ResultGetSyncStatus struct { + Sync uint32 `json:"syncheight"` + Current uint32 `json:"factomheight"` +} diff --git a/cli/cmd/complete.go b/cli/complete.go similarity index 99% rename from cli/cmd/complete.go rename to cli/complete.go index 7d9bca2..00d0097 100644 --- a/cli/cmd/complete.go +++ b/cli/complete.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( goflag "flag" diff --git a/cli/cmd/get.go b/cli/get.go similarity index 99% rename from cli/cmd/get.go rename to cli/get.go index a04c04d..e565ed0 100644 --- a/cli/cmd/get.go +++ b/cli/get.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "github.com/posener/complete" diff --git a/cli/cmd/getbalance.go b/cli/getbalance.go similarity index 93% rename from cli/cmd/getbalance.go rename to cli/getbalance.go index 257debc..5ef1fa4 100644 --- a/cli/cmd/getbalance.go +++ b/cli/getbalance.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" @@ -28,9 +28,9 @@ import ( "math" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/Factom-Asset-Tokens/fatd/srv" "github.com/posener/complete" "github.com/spf13/cobra" ) @@ -98,12 +98,12 @@ func getBalanceArgs(cmd *cobra.Command, args []string) error { func getBalance(cmd *cobra.Command, _ []string) { if paramsToken.ChainID == nil { - var params srv.ParamsGetBalances + var params api.ParamsGetBalances params.IncludePending = paramsToken.IncludePending vrbLog.Println("Fetching balances for all chains...") for _, adr := range addresses { params.Address = &adr - var balances srv.ResultGetBalances + var balances api.ResultGetBalances if err := FATClient.Request(context.Background(), "get-balances", params, &balances); err != nil { errLog.Fatal(err) @@ -116,8 +116,8 @@ func getBalance(cmd *cobra.Command, _ []string) { fmt.Println() for chainID, balance := range balances { vrbLog.Printf("Fetching token chain details... %v", chainID) - params := srv.ParamsToken{ChainID: &chainID} - var stats srv.ResultGetStats + params := api.ParamsToken{ChainID: &chainID} + var stats api.ResultGetStats if err := FATClient.Request(context.Background(), "get-stats", params, &stats); err != nil { errLog.Fatal(err) @@ -136,15 +136,15 @@ func getBalance(cmd *cobra.Command, _ []string) { } vrbLog.Printf("Fetching token chain details... %v", paramsToken.ChainID) - params := srv.ParamsToken{ChainID: paramsToken.ChainID} - var stats srv.ResultGetStats + params := api.ParamsToken{ChainID: paramsToken.ChainID} + var stats api.ResultGetStats if err := FATClient.Request(context.Background(), "get-stats", params, &stats); err != nil { errLog.Fatal(err) } switch stats.Issuance.Type { case fat0.Type: - var params srv.ParamsGetBalance + var params api.ParamsGetBalance params.ChainID = paramsToken.ChainID params.IncludePending = paramsToken.IncludePending vrbLog.Println("Fetching balances...") @@ -163,7 +163,7 @@ func getBalance(cmd *cobra.Command, _ []string) { } } case fat1.Type: - var params srv.ParamsGetNFBalance + var params api.ParamsGetNFBalance params.Limit = math.MaxUint64 params.ChainID = paramsToken.ChainID params.IncludePending = paramsToken.IncludePending diff --git a/cli/cmd/getchains.go b/cli/getchains.go similarity index 94% rename from cli/cmd/getchains.go rename to cli/getchains.go index 4a726c3..a484669 100644 --- a/cli/cmd/getchains.go +++ b/cli/getchains.go @@ -20,14 +20,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" "fmt" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/srv" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/posener/complete" "github.com/spf13/cobra" ) @@ -99,7 +99,7 @@ func getChainsArgs(_ *cobra.Command, args []string) error { func getChains(_ *cobra.Command, _ []string) { if len(chainIDs) == 0 { vrbLog.Println("Fetching list of issued token chains...") - var chains []srv.ParamsToken + var chains []api.ParamsToken if err := FATClient.Request(context.Background(), "get-daemon-tokens", nil, &chains); err != nil { errLog.Fatal(err) @@ -116,9 +116,9 @@ Token ID: %q for _, chainID := range chainIDs { vrbLog.Printf("Fetching token chain details... %v", chainID) - params := srv.ParamsToken{ChainID: &chainID, + params := api.ParamsToken{ChainID: &chainID, IncludePending: paramsToken.IncludePending} - var stats srv.ResultGetStats + var stats api.ResultGetStats if err := FATClient.Request(context.Background(), "get-stats", params, &stats); err != nil { errLog.Fatal(err) @@ -127,7 +127,7 @@ Token ID: %q } } -func printStats(chainID *factom.Bytes32, stats srv.ResultGetStats) { +func printStats(chainID *factom.Bytes32, stats api.ResultGetStats) { fmt.Printf(`Chain ID: %v Issuer Identity Chain ID: %v Issuance Entry Hash: %v diff --git a/cli/cmd/gettransactions.go b/cli/gettransactions.go similarity index 94% rename from cli/cmd/gettransactions.go rename to cli/gettransactions.go index 5adf2da..d30b4ad 100644 --- a/cli/cmd/gettransactions.go +++ b/cli/gettransactions.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" @@ -30,19 +30,19 @@ import ( "time" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/Factom-Asset-Tokens/fatd/srv" "github.com/posener/complete" "github.com/spf13/cobra" ) var ( - paramsGetTxs = srv.ParamsGetTransactions{ + paramsGetTxs = api.ParamsGetTransactions{ StartHash: new(factom.Bytes32), NFTokenID: new(fat1.NFTokenID), - ParamsToken: srv.ParamsToken{ChainID: paramsToken.ChainID}, - ParamsPagination: srv.ParamsPagination{Page: new(uint), + ParamsToken: api.ParamsToken{ChainID: paramsToken.ChainID}, + ParamsPagination: api.ParamsPagination{Page: new(uint), Order: "desc"}, } to, from bool @@ -182,7 +182,7 @@ func getTxs(_ *cobra.Command, _ []string) { vrbLog.Printf("Fetching txs for chain... %v", paramsToken.ChainID) if len(transactionIDs) == 0 { - result := make([]srv.ResultGetTransaction, paramsGetTxs.Limit) + result := make([]api.ResultGetTransaction, paramsGetTxs.Limit) for i := range result { result[i].Tx = &json.RawMessage{} } @@ -195,8 +195,8 @@ func getTxs(_ *cobra.Command, _ []string) { } return } - params := srv.ParamsGetTransaction{ParamsToken: paramsGetTxs.ParamsToken} - var result srv.ResultGetTransaction + params := api.ParamsGetTransaction{ParamsToken: paramsGetTxs.ParamsToken} + var result api.ResultGetTransaction var tx json.RawMessage result.Tx = &tx for _, txID := range transactionIDs { @@ -211,7 +211,7 @@ func getTxs(_ *cobra.Command, _ []string) { return } -func printTx(result srv.ResultGetTransaction) { +func printTx(result api.ResultGetTransaction) { if result.Pending { fmt.Println("PENDING TX") } diff --git a/cli/cmd/issue.go b/cli/issue.go similarity index 98% rename from cli/cmd/issue.go rename to cli/issue.go index bcaa443..0fb2195 100644 --- a/cli/cmd/issue.go +++ b/cli/issue.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" @@ -29,8 +29,8 @@ import ( "strings" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/srv" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/posener/complete" @@ -225,12 +225,12 @@ func validateIssueFlags(cmd *cobra.Command, args []string) error { } vrbLog.Println("Checking token chain status...") - params := srv.ParamsToken{ChainID: paramsToken.ChainID} - var stats srv.ResultGetStats + params := api.ParamsToken{ChainID: paramsToken.ChainID} + var stats api.ResultGetStats if err := FATClient.Request(context.Background(), "get-stats", params, &stats); err != nil { rpcErr, _ := err.(jsonrpc2.Error) - if rpcErr != srv.ErrorTokenNotFound { + if rpcErr != api.ErrorTokenNotFound { errLog.Fatal(err) } } else { diff --git a/cli/main.go b/cli/main.go index 95f99e7..7e71c5e 100644 --- a/cli/main.go +++ b/cli/main.go @@ -22,11 +22,9 @@ package main -import "github.com/Factom-Asset-Tokens/fatd/cli/cmd" - func main() { - if cmd.Complete() { + if Complete() { return } - cmd.Execute() + Execute() } diff --git a/cli/cmd/predict.go b/cli/predict.go similarity index 97% rename from cli/cmd/predict.go rename to cli/predict.go index 4b010cc..a163471 100644 --- a/cli/cmd/predict.go +++ b/cli/predict.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" @@ -30,7 +30,7 @@ import ( "time" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/srv" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/posener/complete" ) @@ -112,7 +112,7 @@ var PredictChainIDs complete.PredictFunc = func(args complete.Args) []string { if err := parseAPIFlags(); err != nil { return nil } - var chains []srv.ParamsToken + var chains []api.ParamsToken if err := FATClient.Request(context.Background(), "get-daemon-tokens", nil, &chains); err != nil { logErr(err) diff --git a/cli/cmd/root.go b/cli/root.go similarity index 98% rename from cli/cmd/root.go rename to cli/root.go index 6b3476f..ba93834 100644 --- a/cli/cmd/root.go +++ b/cli/root.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" @@ -33,8 +33,8 @@ import ( "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/srv" homedir "github.com/mitchellh/go-homedir" "github.com/posener/complete" "github.com/spf13/cobra" @@ -108,7 +108,7 @@ var ( Verbose bool cfgFile string - FATClient = srv.NewClient() + FATClient = api.NewClient() FactomClient = factom.NewClient() Debug bool @@ -116,7 +116,7 @@ var ( Version bool - paramsToken = srv.ParamsToken{ + paramsToken = api.ParamsToken{ ChainID: new(factom.Bytes32), IssuerChainID: new(factom.Bytes32)} NameIDs []factom.Bytes @@ -400,7 +400,7 @@ You must re-open your shell before completion changes take effect.`[1:]) func printVersions() { fmt.Printf("fat-cli: %v\n", Revision) vrbLog.Println("Fetching fatd properties...") - var properties srv.ResultGetDaemonProperties + var properties api.ResultGetDaemonProperties if err := FATClient.Request(context.Background(), "get-daemon-properties", nil, &properties); err != nil { errLog.Fatal(err) diff --git a/cli/cmd/transact.go b/cli/transact.go similarity index 96% rename from cli/cmd/transact.go rename to cli/transact.go index c0a88f5..e870a52 100644 --- a/cli/cmd/transact.go +++ b/cli/transact.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "context" @@ -29,10 +29,10 @@ import ( "math" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/Factom-Asset-Tokens/fatd/srv" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/posener/complete" @@ -251,8 +251,8 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { if !force { vrbLog.Println("Checking token chain status...") - params := srv.ParamsToken{ChainID: paramsToken.ChainID} - var stats srv.ResultGetStats + params := api.ParamsToken{ChainID: paramsToken.ChainID} + var stats api.ResultGetStats if err := FATClient.Request(context.Background(), "get-stats", params, &stats); err != nil { errLog.Fatal(err) @@ -264,7 +264,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { } if inputSet { - paramsGetBalance := srv.ParamsGetBalance{ParamsToken: params} + paramsGetBalance := api.ParamsGetBalance{ParamsToken: params} for _, adr := range inputAdrs { vrbLog.Println("Checking FAT Token balance...", adr) paramsGetBalance.Address = &adr @@ -289,7 +289,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { } } if inputSet && cmdType == fat1.Type { - params := srv.ParamsGetNFBalance{ParamsToken: params} + params := api.ParamsGetNFBalance{ParamsToken: params} params.Limit = math.MaxUint64 for _, adr := range inputAdrs { vrbLog.Println("Checking FAT NF Token ownership...", adr) @@ -328,7 +328,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { "invalid coinbase transaction: exceeds max supply") } if cmdType == fat1.Type { - params := srv.ParamsGetNFToken{ParamsToken: params} + params := api.ParamsGetNFToken{ParamsToken: params} for tknID := range fat1Tx.Inputs.AllNFTokens() { params.NFTokenID = &tknID err := FATClient.Request(context.Background(), @@ -338,7 +338,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { tknID) } rpcErr, _ := err.(jsonrpc2.Error) - if rpcErr.Code != srv.ErrorTokenNotFound.Code { + if rpcErr.Code != api.ErrorTokenNotFound.Code { errLog.Fatal(err) } } diff --git a/cli/cmd/transactfat0.go b/cli/transactfat0.go similarity index 99% rename from cli/cmd/transactfat0.go rename to cli/transactfat0.go index e24010c..396dde6 100644 --- a/cli/cmd/transactfat0.go +++ b/cli/transactfat0.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "fmt" diff --git a/cli/cmd/transactfat1.go b/cli/transactfat1.go similarity index 99% rename from cli/cmd/transactfat1.go rename to cli/transactfat1.go index a510a77..352b2f0 100644 --- a/cli/cmd/transactfat1.go +++ b/cli/transactfat1.go @@ -20,7 +20,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package cmd +package main import ( "fmt" diff --git a/db/addresses/addresses.go b/internal/db/addresses/addresses.go similarity index 100% rename from db/addresses/addresses.go rename to internal/db/addresses/addresses.go diff --git a/db/addresses/txrelation.go b/internal/db/addresses/txrelation.go similarity index 100% rename from db/addresses/txrelation.go rename to internal/db/addresses/txrelation.go diff --git a/db/apply.go b/internal/db/apply.go similarity index 96% rename from db/apply.go rename to internal/db/apply.go index 948a271..095fe51 100644 --- a/db/apply.go +++ b/internal/db/apply.go @@ -27,11 +27,11 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/addresses" - "github.com/Factom-Asset-Tokens/fatd/db/eblocks" - "github.com/Factom-Asset-Tokens/fatd/db/entries" - "github.com/Factom-Asset-Tokens/fatd/db/metadata" - "github.com/Factom-Asset-Tokens/fatd/db/nftokens" + "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" + "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" + "github.com/Factom-Asset-Tokens/fatd/internal/db/metadata" + "github.com/Factom-Asset-Tokens/fatd/internal/db/nftokens" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" diff --git a/db/chain.go b/internal/db/chain.go similarity index 96% rename from db/chain.go rename to internal/db/chain.go index 3e9ae4d..32a6f04 100644 --- a/db/chain.go +++ b/internal/db/chain.go @@ -34,12 +34,12 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/addresses" - "github.com/Factom-Asset-Tokens/fatd/db/eblocks" - "github.com/Factom-Asset-Tokens/fatd/db/entries" - "github.com/Factom-Asset-Tokens/fatd/db/metadata" + "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" + "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" + "github.com/Factom-Asset-Tokens/fatd/internal/db/metadata" "github.com/Factom-Asset-Tokens/fatd/fat" - _log "github.com/Factom-Asset-Tokens/fatd/log" + _log "github.com/Factom-Asset-Tokens/fatd/internal/log" ) var ( diff --git a/db/chain_test.go b/internal/db/chain_test.go similarity index 96% rename from db/chain_test.go rename to internal/db/chain_test.go index 064f8e0..32891aa 100644 --- a/db/chain_test.go +++ b/internal/db/chain_test.go @@ -26,7 +26,7 @@ import ( "context" "testing" - "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/db/eblocks/eblocks.go b/internal/db/eblocks/eblocks.go similarity index 100% rename from db/eblocks/eblocks.go rename to internal/db/eblocks/eblocks.go diff --git a/db/entries/entries.go b/internal/db/entries/entries.go similarity index 99% rename from db/entries/entries.go rename to internal/db/entries/entries.go index d7df4a4..fc92f04 100644 --- a/db/entries/entries.go +++ b/internal/db/entries/entries.go @@ -32,7 +32,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/sqlbuilder" + "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) diff --git a/db/gen.go b/internal/db/gen.go similarity index 100% rename from db/gen.go rename to internal/db/gen.go diff --git a/db/gentestdb.go b/internal/db/gentestdb.go similarity index 96% rename from db/gentestdb.go rename to internal/db/gentestdb.go index db07b5b..468af2d 100644 --- a/db/gentestdb.go +++ b/internal/db/gentestdb.go @@ -32,9 +32,9 @@ import ( "os" . "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/fat" - fflag "github.com/Factom-Asset-Tokens/fatd/flag" + fflag "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) func init() { diff --git a/db/metadata/metadata.go b/internal/db/metadata/metadata.go similarity index 98% rename from db/metadata/metadata.go rename to internal/db/metadata/metadata.go index b39cc64..85a878c 100644 --- a/db/metadata/metadata.go +++ b/internal/db/metadata/metadata.go @@ -30,7 +30,7 @@ import ( "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" ) diff --git a/db/nftokens/nftokens.go b/internal/db/nftokens/nftokens.go similarity index 99% rename from db/nftokens/nftokens.go rename to internal/db/nftokens/nftokens.go index cee39d4..bdd4633 100644 --- a/db/nftokens/nftokens.go +++ b/internal/db/nftokens/nftokens.go @@ -32,7 +32,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/sqlbuilder" + "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) diff --git a/db/nftokens/txrelation.go b/internal/db/nftokens/txrelation.go similarity index 100% rename from db/nftokens/txrelation.go rename to internal/db/nftokens/txrelation.go diff --git a/db/schema.go b/internal/db/schema.go similarity index 89% rename from db/schema.go rename to internal/db/schema.go index 0552b70..eed04e1 100644 --- a/db/schema.go +++ b/internal/db/schema.go @@ -27,11 +27,11 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" - "github.com/Factom-Asset-Tokens/fatd/db/addresses" - "github.com/Factom-Asset-Tokens/fatd/db/eblocks" - "github.com/Factom-Asset-Tokens/fatd/db/entries" - "github.com/Factom-Asset-Tokens/fatd/db/metadata" - "github.com/Factom-Asset-Tokens/fatd/db/nftokens" + "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" + "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" + "github.com/Factom-Asset-Tokens/fatd/internal/db/metadata" + "github.com/Factom-Asset-Tokens/fatd/internal/db/nftokens" ) const ( diff --git a/db/sqlbuilder/sqlbuilder.go b/internal/db/sqlbuilder/sqlbuilder.go similarity index 100% rename from db/sqlbuilder/sqlbuilder.go rename to internal/db/sqlbuilder/sqlbuilder.go diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 b/internal/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 similarity index 100% rename from db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 rename to internal/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 diff --git a/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 b/internal/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 similarity index 100% rename from db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 rename to internal/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 diff --git a/db/validate.go b/internal/db/validate.go similarity index 97% rename from db/validate.go rename to internal/db/validate.go index 0005aee..91f6154 100644 --- a/db/validate.go +++ b/internal/db/validate.go @@ -30,10 +30,10 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/AdamSLevy/sqlitechangeset" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/eblocks" - "github.com/Factom-Asset-Tokens/fatd/db/entries" + "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) func init() { diff --git a/engine/chain.go b/internal/engine/chain.go similarity index 97% rename from engine/chain.go rename to internal/engine/chain.go index b718da4..18dc02b 100644 --- a/engine/chain.go +++ b/internal/engine/chain.go @@ -32,10 +32,10 @@ import ( jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/flag" - _log "github.com/Factom-Asset-Tokens/fatd/log" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" + _log "github.com/Factom-Asset-Tokens/fatd/internal/log" ) type Chain struct { diff --git a/engine/chainmap.go b/internal/engine/chainmap.go similarity index 98% rename from engine/chainmap.go rename to internal/engine/chainmap.go index 3d9b9ef..0552b44 100644 --- a/engine/chainmap.go +++ b/internal/engine/chainmap.go @@ -29,8 +29,8 @@ import ( "sync" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db" - "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/db" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) var ( diff --git a/engine/chainstatus.go b/internal/engine/chainstatus.go similarity index 100% rename from engine/chainstatus.go rename to internal/engine/chainstatus.go diff --git a/engine/chainstatus_test.go b/internal/engine/chainstatus_test.go similarity index 100% rename from engine/chainstatus_test.go rename to internal/engine/chainstatus_test.go diff --git a/engine/engine.go b/internal/engine/engine.go similarity index 98% rename from engine/engine.go rename to internal/engine/engine.go index 81acec1..29b83bd 100644 --- a/engine/engine.go +++ b/internal/engine/engine.go @@ -33,8 +33,8 @@ import ( "github.com/nightlyone/lockfile" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/flag" - _log "github.com/Factom-Asset-Tokens/fatd/log" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" + _log "github.com/Factom-Asset-Tokens/fatd/internal/log" ) var ( diff --git a/engine/process.go b/internal/engine/process.go similarity index 98% rename from engine/process.go rename to internal/engine/process.go index 699a2cd..621497f 100644 --- a/engine/process.go +++ b/internal/engine/process.go @@ -31,9 +31,9 @@ import ( "crawshaw.io/sqlite" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db" + "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) error { diff --git a/flag/flag.go b/internal/flag/flag.go similarity index 100% rename from flag/flag.go rename to internal/flag/flag.go diff --git a/flag/list.go b/internal/flag/list.go similarity index 100% rename from flag/list.go rename to internal/flag/list.go diff --git a/flag/predict.go b/internal/flag/predict.go similarity index 100% rename from flag/predict.go rename to internal/flag/predict.go diff --git a/log/log.go b/internal/log/log.go similarity index 96% rename from log/log.go rename to internal/log/log.go index 4c01c3a..a9f7796 100644 --- a/log/log.go +++ b/internal/log/log.go @@ -23,7 +23,7 @@ package log import ( - "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" "github.com/sirupsen/logrus" ) diff --git a/srv/doc.go b/internal/srv/doc.go similarity index 100% rename from srv/doc.go rename to internal/srv/doc.go diff --git a/srv/methods.go b/internal/srv/methods.go similarity index 79% rename from srv/methods.go rename to internal/srv/methods.go index 55079d9..b428d9f 100644 --- a/srv/methods.go +++ b/internal/srv/methods.go @@ -32,14 +32,15 @@ import ( jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/db/addresses" - "github.com/Factom-Asset-Tokens/fatd/db/entries" - "github.com/Factom-Asset-Tokens/fatd/db/nftokens" - "github.com/Factom-Asset-Tokens/fatd/engine" + "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/fat/fat0" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - "github.com/Factom-Asset-Tokens/fatd/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" + "github.com/Factom-Asset-Tokens/fatd/internal/db/nftokens" + "github.com/Factom-Asset-Tokens/fatd/internal/engine" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) var c = flag.FactomClient @@ -65,16 +66,9 @@ var jsonrpc2Methods = jsonrpc2.MethodMap{ "get-sync-status": getSyncStatus, } -type ResultGetIssuance struct { - ParamsToken - Hash *factom.Bytes32 `json:"entryhash"` - Timestamp int64 `json:"timestamp"` - Issuance fat.Issuance `json:"issuance"` -} - func getIssuance(entry bool) jsonrpc2.MethodFunc { return func(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsToken + var params api.ParamsToken chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -84,8 +78,8 @@ func getIssuance(entry bool) jsonrpc2.MethodFunc { if entry { return chain.Issuance.Entry.Entry } - return ResultGetIssuance{ - ParamsToken: ParamsToken{ + return api.ResultGetIssuance{ + ParamsToken: api.ParamsToken{ ChainID: chain.ID, TokenID: chain.TokenID, IssuerChainID: chain.Identity.ChainID, @@ -97,16 +91,9 @@ func getIssuance(entry bool) jsonrpc2.MethodFunc { } } -type ResultGetTransaction struct { - Hash *factom.Bytes32 `json:"entryhash"` - Timestamp int64 `json:"timestamp"` - Tx interface{} `json:"data"` - Pending bool `json:"pending,omitempty"` -} - func getTransaction(getEntry bool) jsonrpc2.MethodFunc { return func(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetTransaction + var params api.ParamsGetTransaction chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -118,14 +105,14 @@ func getTransaction(getEntry bool) jsonrpc2.MethodFunc { panic(err) } if !entry.IsPopulated() { - return ErrorTransactionNotFound + return api.ErrorTransactionNotFound } if getEntry { return entry } - result := ResultGetTransaction{ + result := api.ResultGetTransaction{ Hash: entry.Hash, Timestamp: entry.Timestamp.Unix(), Pending: chain.LatestEntryTimestamp().Before(entry.Timestamp), @@ -150,7 +137,7 @@ func getTransaction(getEntry bool) jsonrpc2.MethodFunc { func getTransactions(getEntry bool) jsonrpc2.MethodFunc { return func(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetTransactions + var params api.ParamsGetTransactions chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -158,7 +145,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { defer put() if params.NFTokenID != nil && chain.Type != fat1.Type { - err := ErrorTokenNotFound + err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err } @@ -176,7 +163,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { panic(err) } if len(entries) == 0 { - return ErrorTransactionNotFound + return api.ErrorTransactionNotFound } if getEntry { // Omit the ChainID from the response since the client @@ -187,7 +174,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { return entries } - txs := make([]ResultGetTransaction, len(entries)) + txs := make([]api.ResultGetTransaction, len(entries)) for i := range txs { entry := entries[i] var tx fat.Transaction @@ -213,7 +200,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { } func getBalance(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetBalance + var params api.ParamsGetBalance chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -227,40 +214,14 @@ func getBalance(ctx context.Context, data json.RawMessage) interface{} { return balance } -type ResultGetBalances map[factom.Bytes32]uint64 - -func (r ResultGetBalances) MarshalJSON() ([]byte, error) { - strMap := make(map[string]uint64, len(r)) - for chainID, balance := range r { - strMap[chainID.String()] = balance - } - return json.Marshal(strMap) -} - -func (r *ResultGetBalances) UnmarshalJSON(data []byte) error { - var strMap map[string]uint64 - if err := json.Unmarshal(data, &strMap); err != nil { - return err - } - *r = make(map[factom.Bytes32]uint64, len(strMap)) - var chainID factom.Bytes32 - for str, balance := range strMap { - if err := chainID.Set(str); err != nil { - return err - } - (*r)[chainID] = balance - } - return nil -} - func getBalances(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetBalances + var params api.ParamsGetBalances if _, _, err := validate(ctx, data, ¶ms); err != nil { return err } issuedIDs := engine.Chains.GetIssued() - balances := make(ResultGetBalances, len(issuedIDs)) + balances := make(api.ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) put := chain.Get(ctx, params.HasIncludePending()) @@ -277,7 +238,7 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { } func getNFBalance(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetNFBalance + var params api.ParamsGetNFBalance chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -285,7 +246,7 @@ func getNFBalance(ctx context.Context, data json.RawMessage) interface{} { defer put() if chain.Type != fat1.Type { - err := ErrorTokenNotFound + err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err } @@ -305,22 +266,10 @@ func getNFBalance(ctx context.Context, data json.RawMessage) interface{} { return tkns } -type ResultGetStats struct { - ParamsToken - Issuance *fat.Issuance - IssuanceHash *factom.Bytes32 - CirculatingSupply uint64 `json:"circulating"` - Burned uint64 `json:"burned"` - Transactions int64 `json:"transactions"` - IssuanceTimestamp int64 `json:"issuancets"` - LastTransactionTimestamp int64 `json:"lasttxts,omitempty"` - NonZeroBalances int64 `json:"nonzerobalances, omitempty"` -} - var coinbaseRCDHash = fat.Coinbase() func getStats(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsToken + var params api.ParamsToken chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -342,7 +291,7 @@ func getStats(ctx context.Context, data json.RawMessage) interface{} { panic(err) } - res := ResultGetStats{ + res := api.ResultGetStats{ CirculatingSupply: chain.NumIssued - burned, Burned: burned, Transactions: txCount, @@ -360,16 +309,8 @@ func getStats(ctx context.Context, data json.RawMessage) interface{} { return res } -type ResultGetNFToken struct { - NFTokenID fat1.NFTokenID `json:"id"` - Owner *factom.FAAddress `json:"owner,omitempty"` - Burned bool `json:"burned,omitempty"` - Metadata json.RawMessage `json:"metadata,omitempty"` - CreationTx *factom.Bytes32 `json:"creationtx"` -} - func getNFToken(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetNFToken + var params api.ParamsGetNFToken chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -377,7 +318,7 @@ func getNFToken(ctx context.Context, data json.RawMessage) interface{} { defer put() if chain.Type != fat1.Type { - err := ErrorTokenNotFound + err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err } @@ -388,12 +329,12 @@ func getNFToken(ctx context.Context, data json.RawMessage) interface{} { panic(err) } if creationHash.IsZero() { - err := ErrorTokenNotFound + err := api.ErrorTokenNotFound err.Data = "No such NFTokenID has been issued" return err } - res := ResultGetNFToken{ + res := api.ResultGetNFToken{ NFTokenID: *params.NFTokenID, Metadata: metadata, Owner: &owner, @@ -408,7 +349,7 @@ func getNFToken(ctx context.Context, data json.RawMessage) interface{} { } func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsGetAllNFTokens + var params api.ParamsGetAllNFTokens chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err @@ -416,7 +357,7 @@ func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { defer put() if chain.Type != fat1.Type { - err := ErrorTokenNotFound + err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err } @@ -427,7 +368,7 @@ func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { panic(err) } - res := make([]ResultGetNFToken, len(tkns)) + res := make([]api.ResultGetNFToken, len(tkns)) for i := range res { res[i].NFTokenID = tkns[i] res[i].Metadata = metadata[i] @@ -443,14 +384,14 @@ func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { } func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { - var params ParamsSendTransaction + var params api.ParamsSendTransaction chain, put, err := validate(ctx, data, ¶ms) if err != nil { return err } defer put() if !params.DryRun && factom.Bytes32(flag.EsAdr).IsZero() { - return ErrorNoEC + return api.ErrorNoEC } entry := params.Entry() @@ -465,7 +406,7 @@ func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { panic(err) } if txErr != nil { - err := ErrorInvalidTransaction + err := api.ErrorInvalidTransaction err.Data = txErr.Error() return err } @@ -478,7 +419,7 @@ func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { } cost, _ := entry.Cost() if balance < uint64(cost) { - return ErrorNoEC + return api.ErrorNoEC } txID = new(factom.Bytes32) var commit []byte @@ -605,7 +546,7 @@ func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { } issuedIDs := engine.Chains.GetIssued() - chains := make([]ParamsToken, len(issuedIDs)) + chains := make([]api.ParamsToken, len(issuedIDs)) for i, chainID := range issuedIDs { chain := engine.Chains.Get(chainID) chainID := chainID @@ -616,35 +557,24 @@ func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { return chains } -type ResultGetDaemonProperties struct { - FatdVersion string `json:"fatdversion"` - APIVersion string `json:"apiversion"` - NetworkID factom.NetworkID `json:"factomnetworkid"` -} - func getDaemonProperties(ctx context.Context, data json.RawMessage) interface{} { if _, _, err := validate(ctx, data, nil); err != nil { return err } - return ResultGetDaemonProperties{ + return api.ResultGetDaemonProperties{ FatdVersion: flag.Revision, APIVersion: APIVersion, NetworkID: flag.NetworkID, } } -type ResultGetSyncStatus struct { - Sync uint32 `json:"syncheight"` - Current uint32 `json:"factomheight"` -} - func getSyncStatus(ctx context.Context, data json.RawMessage) interface{} { sync, current := engine.GetSyncStatus() - return ResultGetSyncStatus{Sync: sync, Current: current} + return api.ResultGetSyncStatus{Sync: sync, Current: current} } func validate(ctx context.Context, - data json.RawMessage, params Params) (*engine.Chain, func(), error) { + data json.RawMessage, params api.Params) (*engine.Chain, func(), error) { if params == nil { if len(data) > 0 { return nil, nil, jsonrpc2.ErrorInvalidParams( @@ -662,13 +592,13 @@ func validate(ctx context.Context, return nil, nil, err } if params.HasIncludePending() && flag.DisablePending { - return nil, nil, ErrorPendingDisabled + return nil, nil, api.ErrorPendingDisabled } chainID := params.ValidChainID() if chainID != nil { chain := engine.Chains.Get(chainID) if !chain.IsIssued() { - return nil, nil, ErrorTokenNotFound + return nil, nil, api.ErrorTokenNotFound } ctx, cancel := context.WithTimeout(ctx, 2*time.Second) put := chain.Get(ctx, params.HasIncludePending()) diff --git a/srv/srv.go b/internal/srv/srv.go similarity index 96% rename from srv/srv.go rename to internal/srv/srv.go index bc73c2c..e817c44 100644 --- a/srv/srv.go +++ b/internal/srv/srv.go @@ -28,8 +28,8 @@ import ( "time" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" - "github.com/Factom-Asset-Tokens/fatd/flag" - _log "github.com/Factom-Asset-Tokens/fatd/log" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" + _log "github.com/Factom-Asset-Tokens/fatd/internal/log" "github.com/goji/httpauth" "github.com/rs/cors" ) diff --git a/main.go b/main.go index d47c28e..0da5dbe 100644 --- a/main.go +++ b/main.go @@ -27,10 +27,10 @@ import ( "os" "os/signal" - "github.com/Factom-Asset-Tokens/fatd/engine" - "github.com/Factom-Asset-Tokens/fatd/flag" - "github.com/Factom-Asset-Tokens/fatd/log" - "github.com/Factom-Asset-Tokens/fatd/srv" + "github.com/Factom-Asset-Tokens/fatd/internal/engine" + "github.com/Factom-Asset-Tokens/fatd/internal/flag" + "github.com/Factom-Asset-Tokens/fatd/internal/log" + "github.com/Factom-Asset-Tokens/fatd/internal/srv" ) func main() { os.Exit(_main()) } From 183fbeeced56f88f3a2cee36254ccb8c8f4fd3da Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 11 Oct 2019 15:40:47 -0800 Subject: [PATCH 091/124] build(Makefile): Fix revision --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7acbac4..cab20cf 100644 --- a/Makefile +++ b/Makefile @@ -35,8 +35,8 @@ export GOFLAGS GOFLAGS = -gcflags=all=-trimpath=${PWD} -asmflags=all=-trimpath=${PWD} GO_LDFLAGS = -extldflags=$(LDFLAGS) -X github.com/Factom-Asset-Tokens/fatd -FATD_LDFLAGS = "$(GO_LDFLAGS)/flag.Revision=$(REVISION)" -CLI_LDFLAGS = "$(GO_LDFLAGS)/cli/cmd.Revision=$(REVISION)" +FATD_LDFLAGS = "$(GO_LDFLAGS)/internal/flag.Revision=$(REVISION)" +CLI_LDFLAGS = "$(GO_LDFLAGS)/cli.Revision=$(REVISION)" DEPSRC = go.mod go.sum SRC = $(DEPSRC) $(filter-out %_test.go,$(wildcard *.go */*.go */*/*.go)) From 68ffcf992741ca2630e914c591755fd06a9fc34b Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 12 Oct 2019 12:53:54 -0800 Subject: [PATCH 092/124] fix(db): Allow send to self txs in address_transaction schema Since transactions may now have overlapping inputs and outputs, there can be two rows in the "address_transaction" table with the same "address_id" and "entry_id" but different values for "to". This causes a PRIMARY_KEY constraint violation. Include "to" into the composite primary key to resolve this. BREAKING CHANGE: This changes the schema and will break existing databases. fix #49 --- internal/db/addresses/txrelation.go | 3 ++- internal/db/apply.go | 6 +++--- internal/db/chain.go | 2 +- internal/db/entries/entries.go | 2 +- internal/db/gentestdb.go | 2 +- internal/db/metadata/metadata.go | 2 +- internal/db/nftokens/nftokens.go | 2 +- internal/db/validate.go | 2 +- internal/engine/chain.go | 2 +- internal/engine/chainmap.go | 2 +- internal/engine/engine.go | 3 ++- internal/engine/process.go | 32 ++++++++++++++--------------- 12 files changed, 31 insertions(+), 29 deletions(-) diff --git a/internal/db/addresses/txrelation.go b/internal/db/addresses/txrelation.go index c5f36eb..e0b5c7e 100644 --- a/internal/db/addresses/txrelation.go +++ b/internal/db/addresses/txrelation.go @@ -12,12 +12,13 @@ const CreateTableTransactions = `CREATE TABLE "address_transactions" ( "address_id" INTEGER NOT NULL, "to" BOOL NOT NULL, - PRIMARY KEY("entry_id", "address_id"), + PRIMARY KEY("entry_id", "address_id", "to"), FOREIGN KEY("entry_id") REFERENCES "entries", FOREIGN KEY("address_id") REFERENCES "addresses" ); CREATE INDEX "idx_address_transactions_address_id" ON "address_transactions"("address_id"); +CREATE INDEX "idx_address_transactions_entry_id" ON "address_transactions"("entry_id"); ` // InsertTransactionRelation inserts a row into "address_transactions" relating diff --git a/internal/db/apply.go b/internal/db/apply.go index 095fe51..f3564c1 100644 --- a/internal/db/apply.go +++ b/internal/db/apply.go @@ -27,14 +27,14 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/fat/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" "github.com/Factom-Asset-Tokens/fatd/internal/db/metadata" "github.com/Factom-Asset-Tokens/fatd/internal/db/nftokens" - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) type applyFunc func(*Chain, int64, factom.Entry) (txErr, err error) diff --git a/internal/db/chain.go b/internal/db/chain.go index 32a6f04..ed62552 100644 --- a/internal/db/chain.go +++ b/internal/db/chain.go @@ -34,11 +34,11 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" "github.com/Factom-Asset-Tokens/fatd/internal/db/metadata" - "github.com/Factom-Asset-Tokens/fatd/fat" _log "github.com/Factom-Asset-Tokens/fatd/internal/log" ) diff --git a/internal/db/entries/entries.go b/internal/db/entries/entries.go index fc92f04..05cf5a5 100644 --- a/internal/db/entries/entries.go +++ b/internal/db/entries/entries.go @@ -32,8 +32,8 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" ) // CreateTable is a SQL string that creates the "entries" table. diff --git a/internal/db/gentestdb.go b/internal/db/gentestdb.go index 468af2d..e38a53d 100644 --- a/internal/db/gentestdb.go +++ b/internal/db/gentestdb.go @@ -32,8 +32,8 @@ import ( "os" . "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/internal/db" fflag "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) diff --git a/internal/db/metadata/metadata.go b/internal/db/metadata/metadata.go index 85a878c..70038d2 100644 --- a/internal/db/metadata/metadata.go +++ b/internal/db/metadata/metadata.go @@ -30,8 +30,8 @@ import ( "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" ) // CreateTable is a SQL string that creates the "metadata" table. diff --git a/internal/db/nftokens/nftokens.go b/internal/db/nftokens/nftokens.go index bdd4633..ac828c6 100644 --- a/internal/db/nftokens/nftokens.go +++ b/internal/db/nftokens/nftokens.go @@ -32,8 +32,8 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" ) // CreateTable is a SQL string that creates the "nf_tokens" table. diff --git a/internal/db/validate.go b/internal/db/validate.go index 91f6154..6d58bd7 100644 --- a/internal/db/validate.go +++ b/internal/db/validate.go @@ -30,9 +30,9 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/AdamSLevy/sqlitechangeset" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" - "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) diff --git a/internal/engine/chain.go b/internal/engine/chain.go index 18dc02b..ddf84a5 100644 --- a/internal/engine/chain.go +++ b/internal/engine/chain.go @@ -32,8 +32,8 @@ import ( jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/internal/flag" _log "github.com/Factom-Asset-Tokens/fatd/internal/log" ) diff --git a/internal/engine/chainmap.go b/internal/engine/chainmap.go index 0552b44..b08d385 100644 --- a/internal/engine/chainmap.go +++ b/internal/engine/chainmap.go @@ -109,7 +109,7 @@ func (cm *ChainMap) Close() { for _, chain := range cm.m { if chain.IsTracked() { // Rollback any pending entries on the chain. - if chain.Pending.OfficialSnapshot != nil { + if chain.Pending.Entries != nil { // Always clean up. if err := chain.revertPending(); err != nil { log.Error(err) diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 29b83bd..eafe4f7 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -341,7 +341,8 @@ func engine(ctx context.Context, done chan struct{}) { if err := ProcessPending( ctx, pe[i:j]...); err != nil { runIfNotDone(ctx, func() { - log.Error(err) + log.Errorf("ChainID(%v): %v", + e.ChainID, err) }) return } diff --git a/internal/engine/process.go b/internal/engine/process.go index 621497f..c7eaa0a 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -31,8 +31,8 @@ import ( "crawshaw.io/sqlite" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/internal/flag" ) @@ -127,6 +127,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err prevStatus := chain.ChainStatus // Apply this EBlock to the chain. + chain.Log.Debugf("Applying EBlock %v...", eb.KeyMR) if err := chain.Apply(ctx, c, dbKeyMR, eb); err != nil { return err } @@ -209,8 +210,8 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { } }() - // Take a snapshot of the official state and copy the current - // official Chain. + // Take a snapshot of the official state and copy the current official + // Chain. s, err := chain.Conn.CreateSnapshot("") if err != nil { return @@ -230,10 +231,10 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { } }() - // SQLite does not guanratee that snapshots will remain - // readable over time due to automatic WAL checkpoints. Thus we - // must keep a read transaction open on the snapshot to prevent - // the WAL autocheckpoints from going past the snapshot. + // SQLite does not guarantee that snapshots will remain readable over + // time due to automatic WAL checkpoints. Thus we must keep a read + // transaction open on the snapshot to prevent the WAL autocheckpoints + // from going past the snapshot. endRead, err := readConn.StartSnapshotRead(s) if err != nil { return err @@ -244,16 +245,15 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { } }() - // Start a new session so we can track all changes and later - // rollback all pending entries. + // Start a new session so we can track all changes and later rollback + // all pending entries. session, err := chain.Conn.CreateSession("") if err != nil { return err } chain.Pending.Session = session chain.Pending.EndSnapshotRead = func() { - // We must clear the interrupt to prevent from - // panicking. + // We must clear the interrupt to prevent from panicking. readConn.SetInterrupt(nil) endRead() chain.Pool.Put(readConn) @@ -270,11 +270,11 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { return err } - // There is a chance the Identity is populated now but wasn't - // before, so update it now. + // There is a chance the Identity is populated now but wasn't before, + // so update it now. if err := chain.Identity.Get(ctx, c); err != nil { - // A jsonrpc2.Error indicates that the identity chain - // doesn't yet exist, which we tolerate. + // A jsonrpc2.Error indicates that the identity chain doesn't + // yet exist, which we tolerate. if _, ok := err.(jsonrpc2.Error); !ok { return err } @@ -326,7 +326,7 @@ func (chain *Chain) Get(ctx context.Context, pending bool) func() { // If pending or if there is no pending state, then use the chain as // is, and just return a function that returns the conn to the pool. - if pending || chain.Pending.OfficialSnapshot == nil { + if pending || chain.Pending.Entries == nil { return func() { chain.Pool.Put(conn) chain.RUnlock() From d66f726c08c155776fdc220a60722e39d86ea8a8 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 12 Oct 2019 20:07:24 -0800 Subject: [PATCH 093/124] refactor(engine): Remove need for RWMutex or Freeing snapshots with sqlite update --- go.mod | 2 +- go.sum | 4 +- internal/engine/chain.go | 14 +++--- internal/engine/chainmap.go | 24 ++++++++++- internal/engine/process.go | 85 ++++++++++++++----------------------- internal/srv/methods.go | 13 +++--- 6 files changed, 70 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index 84c770d..c241bbc 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23 +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f //replace crawshaw.io/sqlite => /home/aslevy/repos/go-modules/AdamSLevy/sqlite diff --git a/go.sum b/go.sum index a07ae34..14c6b03 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7 h1:ZCYtk github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23 h1:rgBx93rHyTKWrAYaRfaD6DZcc6ezXflPtnxRlsG1el4= -github.com/AdamSLevy/sqlite v0.1.3-0.20191009023504-091299abab23/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f h1:45uvVpxAMiLYQAc20fI7R673/zy05LiyCaIyBQy7EQg= +github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d h1:yPm4An70OhM4k4WUq7M9sWaVlFas2+hJB+I3Fsgw38A= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= diff --git a/internal/engine/chain.go b/internal/engine/chain.go index ddf84a5..65da34c 100644 --- a/internal/engine/chain.go +++ b/internal/engine/chain.go @@ -25,7 +25,6 @@ package engine import ( "context" "fmt" - "sync" "crawshaw.io/sqlite" @@ -42,17 +41,17 @@ type Chain struct { ChainStatus db.Chain - *sync.RWMutex - Pending Pending } type Pending struct { - Session *sqlite.Session OfficialSnapshot *sqlite.Snapshot EndSnapshotRead func() - OfficialChain db.Chain - Entries map[factom.Bytes32]factom.Entry + + Session *sqlite.Session + OfficialChain db.Chain + + Entries map[factom.Bytes32]factom.Entry } func (chain Chain) String() string { @@ -65,8 +64,6 @@ func (chain Chain) String() string { func OpenNew(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) (chain Chain, err error) { - chain.RWMutex = new(sync.RWMutex) - var identity factom.Identity identity.ChainID = new(factom.Bytes32) _, *identity.ChainID = fat.TokenIssuer(eb.Entries[0].ExtIDs) @@ -92,7 +89,6 @@ func OpenNew(ctx context.Context, c *factom.Client, } else { chain.ChainStatus = ChainStatusTracked } - chain.RWMutex = new(sync.RWMutex) return } diff --git a/internal/engine/chainmap.go b/internal/engine/chainmap.go index b08d385..cfdfc3f 100644 --- a/internal/engine/chainmap.go +++ b/internal/engine/chainmap.go @@ -28,6 +28,7 @@ import ( "math" "sync" + "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/internal/flag" @@ -69,7 +70,7 @@ func (cm *ChainMap) ignore(id *factom.Bytes32) { cm.set(id, Chain{ChainStatus: ChainStatusIgnored}, ChainStatusIgnored) } -func (cm *ChainMap) Get(id *factom.Bytes32) Chain { +func (cm *ChainMap) get(id *factom.Bytes32) Chain { cm.RLock() defer cm.RUnlock() return cm.m[*id] @@ -164,7 +165,6 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { } chain.Chain = dbChain - chain.RWMutex = new(sync.RWMutex) syncHeight = min(syncHeight, chain.SyncHeight) @@ -178,6 +178,26 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { if err = chain.Validate(); err != nil { return } + } else { + // Ensure WAL file exists which is required for the + // Snapshots used for pending transactions to work. + var begin, commit *sqlite.Stmt + begin, _, err = chain.Conn.PrepareTransient("BEGIN IMMEDIATE;") + if err != nil { + panic(err) + } + defer begin.Finalize() + if _, err = begin.Step(); err != nil { + return + } + commit, _, err = chain.Conn.PrepareTransient("COMMIT;") + if err != nil { + panic(err) + } + defer commit.Finalize() + if _, err = commit.Step(); err != nil { + return + } } if err = chain.Sync(ctx, c); err != nil { diff --git a/internal/engine/process.go b/internal/engine/process.go index c7eaa0a..6613de3 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -37,7 +37,7 @@ import ( ) func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) error { - chain := Chains.Get(eb.ChainID) + chain := Chains.get(eb.ChainID) // Skip ignored chains and if we are ignoring new chain, also skip // unknown chains. @@ -90,9 +90,6 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err return nil } - chain.Lock() - defer chain.Unlock() - // Rollback any pending entries on the chain. if chain.Pending.Entries != nil { chain.Log.Debug("Cleaning up pending state...") @@ -138,16 +135,13 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err } func ProcessPending(ctx context.Context, es ...factom.Entry) error { - chain := Chains.Get(es[0].ChainID) + chain := Chains.get(es[0].ChainID) // We can only apply pending entries to tracked chains. if !chain.IsTracked() { return nil } - chain.Lock() - defer chain.Unlock() - // Initialize Pending if Entries is not yet populated. if chain.Pending.Entries == nil { if err := chain.initPending(ctx); err != nil { @@ -210,34 +204,17 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { } }() - // Take a snapshot of the official state and copy the current official - // Chain. - s, err := chain.Conn.CreateSnapshot("") - if err != nil { - return - } - chain.Pending.OfficialSnapshot = s - defer func() { - if err != nil { - chain.Pending.OfficialSnapshot.Free() - chain.Pending.OfficialSnapshot = nil - } - }() - readConn := chain.Pool.Get(ctx) defer func() { if err != nil { chain.Pool.Put(readConn) } }() - - // SQLite does not guarantee that snapshots will remain readable over - // time due to automatic WAL checkpoints. Thus we must keep a read - // transaction open on the snapshot to prevent the WAL autocheckpoints - // from going past the snapshot. - endRead, err := readConn.StartSnapshotRead(s) + // Take a snapshot of the official state and copy the current official + // Chain. + s, endRead, err := readConn.CreateSnapshot("") if err != nil { - return err + return } defer func() { if err != nil { @@ -251,22 +228,12 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { if err != nil { return err } - chain.Pending.Session = session - chain.Pending.EndSnapshotRead = func() { - // We must clear the interrupt to prevent from panicking. - readConn.SetInterrupt(nil) - endRead() - chain.Pool.Put(readConn) - } defer func() { if err != nil { session.Delete() - chain.Pending.Session = nil - chain.Pending.EndSnapshotRead = nil } }() - - if err := chain.Pending.Session.Attach(""); err != nil { + if err := session.Attach(""); err != nil { return err } @@ -279,6 +246,16 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { return err } } + + chain.Pending.Session = session + chain.Pending.OfficialSnapshot = s + chain.Pending.EndSnapshotRead = func() { + // We must clear the interrupt to prevent from panicking. + readConn.SetInterrupt(nil) + endRead() + chain.Pool.Put(readConn) + } + return nil } func (chain *Chain) revertPending() error { @@ -290,11 +267,10 @@ func (chain *Chain) revertPending() error { // Always clean up our session and snapshots. chain.Pending.EndSnapshotRead() chain.Pending.EndSnapshotRead = nil + chain.Pending.OfficialSnapshot = nil chain.Pending.Session.Delete() chain.Pending.Session = nil - chain.Pending.OfficialSnapshot.Free() - chain.Pending.OfficialSnapshot = nil chain.Conn.SetInterrupt(oldDone) }() // Revert all of the pending transactions by applying the inverse of @@ -318,8 +294,13 @@ func (chain *Chain) revertPending() error { // release the connection back to the pool. If pending is true, the chain will // reflect the state with pending entries applied. Otherwise the chain will // reflect the official state after the most recent EBlock. -func (chain *Chain) Get(ctx context.Context, pending bool) func() { - chain.RLock() +func (cm *ChainMap) Get(ctx context.Context, + id *factom.Bytes32, pending bool) (Chain, func()) { + + cm.RLock() + defer cm.RUnlock() + chain := cm.m[*id] + // Pull a Conn off the Pool and set it as the main Conn. conn := chain.Pool.Get(ctx) chain.Conn = conn @@ -327,15 +308,12 @@ func (chain *Chain) Get(ctx context.Context, pending bool) func() { // If pending or if there is no pending state, then use the chain as // is, and just return a function that returns the conn to the pool. if pending || chain.Pending.Entries == nil { - return func() { + return chain, func() { chain.Pool.Put(conn) - chain.RUnlock() } } - - // Use the official chain state with the conn from the Pool. - chain.Chain = chain.Pending.OfficialChain - chain.Conn = conn + // There are pending entries, but we have been asked for the official + // state. // Start a read transaction on the conn that reflects the official // state. @@ -344,14 +322,17 @@ func (chain *Chain) Get(ctx context.Context, pending bool) func() { panic(err) } + // Use the official chain state with the conn from the Pool. + chain.Chain = chain.Pending.OfficialChain + chain.Conn = conn + // Return a function that ends the read transaction and returns the // conn to the Pool. - return func() { + return chain, func() { // We must clear the interrupt to prevent endRead from // panicking. conn.SetInterrupt(nil) endRead() chain.Pool.Put(conn) - chain.RUnlock() } } diff --git a/internal/srv/methods.go b/internal/srv/methods.go index b428d9f..80d2ca9 100644 --- a/internal/srv/methods.go +++ b/internal/srv/methods.go @@ -223,8 +223,7 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { issuedIDs := engine.Chains.GetIssued() balances := make(api.ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { - chain := engine.Chains.Get(chainID) - put := chain.Get(ctx, params.HasIncludePending()) + chain, put := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) defer put() _, balance, err := addresses.SelectIDBalance(chain.Conn, params.Address) if err != nil { @@ -548,7 +547,8 @@ func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { issuedIDs := engine.Chains.GetIssued() chains := make([]api.ParamsToken, len(issuedIDs)) for i, chainID := range issuedIDs { - chain := engine.Chains.Get(chainID) + chain, put := engine.Chains.Get(ctx, chainID, true) + defer put() chainID := chainID chains[i].ChainID = chainID chains[i].TokenID = chain.TokenID @@ -596,12 +596,13 @@ func validate(ctx context.Context, } chainID := params.ValidChainID() if chainID != nil { - chain := engine.Chains.Get(chainID) + ctx, cancel := context.WithTimeout(ctx, 2*time.Second) + chain, put := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) if !chain.IsIssued() { + cancel() + put() return nil, nil, api.ErrorTokenNotFound } - ctx, cancel := context.WithTimeout(ctx, 2*time.Second) - put := chain.Get(ctx, params.HasIncludePending()) return &chain, func() { cancel(); put() }, nil } return nil, nil, nil From 0840d3b58919ba8a637c8a525448b9bf4ac69795 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Sat, 12 Oct 2019 23:21:36 -0800 Subject: [PATCH 094/124] build(go.mod): Update jsonrpc2/v12 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c241bbc..9f42b1f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb - github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7 + github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191013071151-34ada31112dd github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d diff --git a/go.sum b/go.sum index 14c6b03..6df22d0 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBn github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmKY1f0AITEyfhLaH9zpqIwLgVIKLE+BBCY2B2gys= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7 h1:ZCYtk8/0h7AD2CxrdHLigS+ck6Exl2pRiuJFShetmrQ= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191011020826-f5019a61cce7/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191013071151-34ada31112dd h1:FF4CCtsfTZuMjDnDjjfsdjlT09qIc6lI/V4AurkLNA4= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191013071151-34ada31112dd/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f h1:45uvVpxAMiLYQAc20fI7R673/zy05LiyCaIyBQy7EQg= From 3420b2e940173b537c67c2f6a38bbfc716b9c3e9 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 14 Oct 2019 14:18:24 -0800 Subject: [PATCH 095/124] fix(engine): Improve reliability of Snapshots --- go.mod | 8 ++++-- go.sum | 8 +++--- internal/db/chain.go | 50 ++++++++++++++++++++++++++++++--- internal/engine/chain.go | 1 - internal/engine/chainmap.go | 21 -------------- internal/engine/process.go | 55 ++++++++++++------------------------- internal/srv/methods.go | 9 ++++++ internal/srv/srv.go | 2 +- 8 files changed, 83 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index 9f42b1f..6d4a99f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb - github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191013071151-34ada31112dd + github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191014220836-0f65c00570d0 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d @@ -23,8 +23,10 @@ require ( replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 -replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20191014215059-b98bb18889de + +//replace github.com/Factom-Asset-Tokens/factom => ../factom //replace crawshaw.io/sqlite => /home/aslevy/repos/go-modules/AdamSLevy/sqlite -//replace github.com/Factom-Asset-Tokens/factom => ../factom +//replace github.com/AdamSLevy/jsonrpc2/v12 => /home/aslevy/repos/go-modules/AdamSLevy/jsonrpc2 diff --git a/go.sum b/go.sum index 6df22d0..d2d5b27 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,12 @@ github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBn github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmKY1f0AITEyfhLaH9zpqIwLgVIKLE+BBCY2B2gys= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191013071151-34ada31112dd h1:FF4CCtsfTZuMjDnDjjfsdjlT09qIc6lI/V4AurkLNA4= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191013071151-34ada31112dd/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191014220836-0f65c00570d0 h1:4LQSRw7RFYYvriLgOIT5klY3RmtQv//lUodlPwm3CSY= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191014220836-0f65c00570d0/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= -github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f h1:45uvVpxAMiLYQAc20fI7R673/zy05LiyCaIyBQy7EQg= -github.com/AdamSLevy/sqlite v0.1.3-0.20191013035508-999e58581e1f/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlite v0.1.3-0.20191014215059-b98bb18889de h1:ehn7bnzDZt3ZTXLR5gn0p0NUUl50wZazoi41y7qNrGY= +github.com/AdamSLevy/sqlite v0.1.3-0.20191014215059-b98bb18889de/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d h1:yPm4An70OhM4k4WUq7M9sWaVlFas2+hJB+I3Fsgw38A= github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d/go.mod h1:kQNmmf+2gf3uGKHt0LS4guxdp4Ay44SXA4+Is8/Gxm8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= diff --git a/internal/db/chain.go b/internal/db/chain.go index ed62552..d6b045a 100644 --- a/internal/db/chain.go +++ b/internal/db/chain.go @@ -239,25 +239,67 @@ func OpenConnPool(ctx context.Context, dbURI string) ( return } + // Snapshots are unreliable if auto checkpointing is enabled. So we + // manually checkpoint in the engine every new EBlock and in Close. + if err = sqlitex.ExecScript(conn, + `PRAGMA wal_autocheckpoint = 0;`); err != nil { + return + } + + // Ensure WAL file is created and ready for snapshots by ensuring at + // least one transaction exists in the WAL. + var uv int + if err = sqlitex.Exec(conn, `PRAGMA user_version;`, + func(stmt *sqlite.Stmt) error { + uv = stmt.ColumnInt(0) + return nil + }); err != nil { + return + } + if err = sqlitex.ExecScript(conn, + fmt.Sprintf(`PRAGMA user_version = %v;`, uv)); err != nil { + return + } + + // Open pool flags = baseFlags | sqlite.SQLITE_OPEN_READONLY if pool, err = sqlitex.Open(dbURI, flags, PoolSize); err != nil { err = fmt.Errorf("sqlitex.Open(%q, %x, %v): %w", dbURI, flags, PoolSize, err) return } + defer func() { + if err != nil { + if err := pool.Close(); err != nil { + log.Error(err) + } + } + }() + + // Prime pool for snapshot reads. + for i := 0; i < PoolSize; i++ { + c := pool.Get(nil) + defer pool.Put(c) + if err = sqlitex.ExecScript(c, "PRAGMA application_id;"); err != nil { + return + } + } return } // Close all database connections. Log any errors. func (chain *Chain) Close() { - chain.Conn.SetInterrupt(nil) - sqlitex.ExecScript(chain.Conn, `PRAGMA database.wal_checkpoint;`) if err := chain.Pool.Close(); err != nil { - chain.Log.Errorf("chain.Pool.Close(): %w", err) + chain.Log.Errorf("chain.Pool.Close(): %v", err) + } + chain.Conn.SetInterrupt(nil) + if err := sqlitex.ExecScript(chain.Conn, + `PRAGMA wal_checkpoint;`); err != nil { + chain.Log.Error(err) } // Close this last so that the wal and shm files are removed. if err := chain.Conn.Close(); err != nil { - chain.Log.Errorf("chain.Conn.Close(): %w", err) + chain.Log.Errorf("chain.Conn.Close(): %v", err) } } diff --git a/internal/engine/chain.go b/internal/engine/chain.go index 65da34c..ac133b5 100644 --- a/internal/engine/chain.go +++ b/internal/engine/chain.go @@ -46,7 +46,6 @@ type Chain struct { type Pending struct { OfficialSnapshot *sqlite.Snapshot - EndSnapshotRead func() Session *sqlite.Session OfficialChain db.Chain diff --git a/internal/engine/chainmap.go b/internal/engine/chainmap.go index cfdfc3f..ad93b73 100644 --- a/internal/engine/chainmap.go +++ b/internal/engine/chainmap.go @@ -28,7 +28,6 @@ import ( "math" "sync" - "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/internal/db" "github.com/Factom-Asset-Tokens/fatd/internal/flag" @@ -178,26 +177,6 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { if err = chain.Validate(); err != nil { return } - } else { - // Ensure WAL file exists which is required for the - // Snapshots used for pending transactions to work. - var begin, commit *sqlite.Stmt - begin, _, err = chain.Conn.PrepareTransient("BEGIN IMMEDIATE;") - if err != nil { - panic(err) - } - defer begin.Finalize() - if _, err = begin.Step(); err != nil { - return - } - commit, _, err = chain.Conn.PrepareTransient("COMMIT;") - if err != nil { - panic(err) - } - defer commit.Finalize() - if _, err = commit.Step(); err != nil { - return - } } if err = chain.Sync(ctx, c); err != nil { diff --git a/internal/engine/process.go b/internal/engine/process.go index 6613de3..fda8788 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -29,6 +29,7 @@ import ( "time" "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" @@ -92,7 +93,6 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err // Rollback any pending entries on the chain. if chain.Pending.Entries != nil { - chain.Log.Debug("Cleaning up pending state...") // Load any cached entries that are pending and remove them // from the cache. for i := range eb.Entries { @@ -128,9 +128,14 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err if err := chain.Apply(ctx, c, dbKeyMR, eb); err != nil { return err } + if err := sqlitex.ExecScript(chain.Conn, + `PRAGMA main.wal_checkpoint;`); err != nil { + chain.Log.Error(err) + } // Save the chain back into the map. Chains.set(chain.ID, chain, prevStatus) + return nil } @@ -194,33 +199,10 @@ func ProcessPending(ctx context.Context, es ...factom.Entry) error { func (chain *Chain) initPending(ctx context.Context) (err error) { chain.Log.Debug("Initializing pending...") - chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) - chain.Pending.OfficialChain = chain.Chain - - defer func() { - if err != nil { - chain.Pending.Entries = nil - chain.Pending.OfficialChain = db.Chain{} - } - }() - - readConn := chain.Pool.Get(ctx) - defer func() { - if err != nil { - chain.Pool.Put(readConn) - } - }() - // Take a snapshot of the official state and copy the current official - // Chain. - s, endRead, err := readConn.CreateSnapshot("") + s, err := chain.Pool.GetSnapshot(ctx) if err != nil { return } - defer func() { - if err != nil { - endRead() - } - }() // Start a new session so we can track all changes and later rollback // all pending entries. @@ -247,31 +229,28 @@ func (chain *Chain) initPending(ctx context.Context) (err error) { } } + chain.Pending.Entries = make(map[factom.Bytes32]factom.Entry) + chain.Pending.OfficialChain = chain.Chain chain.Pending.Session = session chain.Pending.OfficialSnapshot = s - chain.Pending.EndSnapshotRead = func() { - // We must clear the interrupt to prevent from panicking. - readConn.SetInterrupt(nil) - endRead() - chain.Pool.Put(readConn) - } return nil } func (chain *Chain) revertPending() error { - chain.Pending.Entries = nil + chain.Log.Debug("Cleaning up pending state...") // We must clear the interrupt to prevent from panicking or being // interrupted while reverting. oldDone := chain.Conn.SetInterrupt(nil) defer func() { + chain.Pending.Entries = nil // Always clean up our session and snapshots. - chain.Pending.EndSnapshotRead() - chain.Pending.EndSnapshotRead = nil chain.Pending.OfficialSnapshot = nil + chain.Pending.OfficialChain = db.Chain{} chain.Pending.Session.Delete() chain.Pending.Session = nil chain.Conn.SetInterrupt(oldDone) + }() // Revert all of the pending transactions by applying the inverse of // the changeset tracked by the session. @@ -297,12 +276,13 @@ func (chain *Chain) revertPending() error { func (cm *ChainMap) Get(ctx context.Context, id *factom.Bytes32, pending bool) (Chain, func()) { - cm.RLock() - defer cm.RUnlock() - chain := cm.m[*id] + chain := cm.get(id) // Pull a Conn off the Pool and set it as the main Conn. conn := chain.Pool.Get(ctx) + if conn == nil { + return Chain{}, nil + } chain.Conn = conn // If pending or if there is no pending state, then use the chain as @@ -319,6 +299,7 @@ func (cm *ChainMap) Get(ctx context.Context, // state. endRead, err := conn.StartSnapshotRead(chain.Pending.OfficialSnapshot) if err != nil { + chain.Pool.Put(conn) panic(err) } diff --git a/internal/srv/methods.go b/internal/srv/methods.go index 80d2ca9..e22ec33 100644 --- a/internal/srv/methods.go +++ b/internal/srv/methods.go @@ -224,6 +224,9 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { balances := make(api.ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { chain, put := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + if put == nil { + return context.Canceled + } defer put() _, balance, err := addresses.SelectIDBalance(chain.Conn, params.Address) if err != nil { @@ -548,6 +551,9 @@ func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { chains := make([]api.ParamsToken, len(issuedIDs)) for i, chainID := range issuedIDs { chain, put := engine.Chains.Get(ctx, chainID, true) + if put == nil { + return context.Canceled + } defer put() chainID := chainID chains[i].ChainID = chainID @@ -598,6 +604,9 @@ func validate(ctx context.Context, if chainID != nil { ctx, cancel := context.WithTimeout(ctx, 2*time.Second) chain, put := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + if put == nil { + return nil, nil, context.Canceled + } if !chain.IsIssued() { cancel() put() diff --git a/internal/srv/srv.go b/internal/srv/srv.go index e817c44..9989e7d 100644 --- a/internal/srv/srv.go +++ b/internal/srv/srv.go @@ -52,7 +52,7 @@ func Start(ctx context.Context) (done <-chan struct{}) { // Set up JSON RPC 2.0 handler with correct headers. jsonrpc2.DebugMethodFunc = true - jrpcHandler := jsonrpc2.HTTPRequestHandler(jsonrpc2Methods) + jrpcHandler := jsonrpc2.HTTPRequestHandler(jsonrpc2Methods, log) var handler http.Handler = http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { From 08e34b8044cb2b092b2985b254364efae74b9cad Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 14 Oct 2019 14:35:22 -0800 Subject: [PATCH 096/124] feat(engine): Clean up logging --- internal/engine/chainmap.go | 3 +-- internal/engine/engine.go | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/engine/chainmap.go b/internal/engine/chainmap.go index ad93b73..899dd24 100644 --- a/internal/engine/chainmap.go +++ b/internal/engine/chainmap.go @@ -95,8 +95,7 @@ func (cm *ChainMap) setSync(height uint32, dbKeyMR *factom.Bytes32) error { continue } if err := chain.SetSync(height, dbKeyMR); err != nil { - chain.Log.Errorf("chain.SetSync(): %v", err) - return err + return fmt.Errorf("chain{%v}.SetSync(): %w", chain.ID, err) } cm.m[*chain.ID] = chain } diff --git a/internal/engine/engine.go b/internal/engine/engine.go index eafe4f7..83479ed 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -184,6 +184,8 @@ func engine(ctx context.Context, done chan struct{}) { if err := lockFile.Unlock(); err != nil { log.Errorf("lockFile.Unlock(): %v", err) } + log.Infof("Synced to block height %v.", syncHeight) + close(done) }() @@ -290,11 +292,6 @@ func engine(ctx context.Context, done chan struct{}) { return } - if flag.LogDebug && h%100 == 0 { - log.Debugf("Synced to block Height: %v KeyMR: %v", - h, dblock.KeyMR) - } - // Check that we haven't been told to stop. select { case <-ctx.Done(): From 7d22c3742c68c512917178c178a8838b5ec52776 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 14 Oct 2019 15:14:40 -0800 Subject: [PATCH 097/124] feat(engine): Default to home dir / network id for database path --- internal/engine/engine.go | 13 +++++++++++-- internal/flag/flag.go | 9 ++++++++- internal/srv/srv.go | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 83479ed..2b879d9 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -27,6 +27,7 @@ import ( "fmt" "os" "runtime" + "strings" "sync" "time" @@ -63,8 +64,16 @@ const ( func Start(ctx context.Context) (done <-chan struct{}) { log = _log.New("pkg", "engine") - // Try to create the main and pending database directories, in case - // they don't already exist. + // Try to create the database directory. + if err := os.Mkdir(flag.DBPath, 0755); err != nil { + if !os.IsExist(err) { + log.Errorf("os.Mkdir(%q): %v", flag.DBPath, err) + return nil + } + } + // Add NetworkID subdirectory. + flag.DBPath += fmt.Sprintf("%s%c", + strings.ReplaceAll(flag.NetworkID.String(), " ", ""), os.PathSeparator) if err := os.Mkdir(flag.DBPath, 0755); err != nil { if !os.IsExist(err) { log.Errorf("os.Mkdir(%q): %v", flag.DBPath, err) diff --git a/internal/flag/flag.go b/internal/flag/flag.go index bcef025..65cacf0 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -88,7 +88,14 @@ var ( "debug": false, "disablepending": false, - "dbpath": "./fatd.db", + "dbpath": func() string { + dbDir := "/fatd.db" + home, err := os.UserHomeDir() + if err != nil { + return "." + dbDir + } + return home + dbDir + }(), "apiaddress": ":8078", "apiusername": "", diff --git a/internal/srv/srv.go b/internal/srv/srv.go index 9989e7d..3c4e788 100644 --- a/internal/srv/srv.go +++ b/internal/srv/srv.go @@ -96,7 +96,7 @@ func Start(ctx context.Context) (done <-chan struct{}) { err = srv.ListenAndServe() } if err != http.ErrServerClosed { - log.Errorf("srv.ListenAndServe(): %w", err) + log.Errorf("srv.ListenAndServe(): %v", err) } close(_done) }() From cae87078dece2f4d010f714eb3f0f71ca0708182 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 14 Oct 2019 15:19:28 -0800 Subject: [PATCH 098/124] test(db): Update test databases --- ...bad3f56169f94966341018b1950542f3dd.sqlite3 | Bin 241664 -> 245760 bytes ...b10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3 | Bin 151552 -> 155648 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/internal/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 b/internal/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3 index 29beba2b5afcf6ba7f7107e9d21562d77a04e973..1ff7aaa1ca260aa895285221cba8386eb54cfe7f 100644 GIT binary patch literal 245760 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

    DO!7o=w!Y1YCAd! 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

    ?hc>+I5Q!9ct8)xbLaJ4kb9>yTE}T3-ZR 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 W)%vS26VeQ{dvVoR$M5B#k$(ZRB(kjl From 5eacde23086ef0c48eb649f30c0813a122b664c0 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 15 Oct 2019 14:50:08 -0800 Subject: [PATCH 099/124] fix(engine,srv): Don't panic on unknown chains --- go.mod | 2 +- go.sum | 4 ++-- internal/engine/process.go | 13 +++++++------ internal/srv/methods.go | 27 +++++++++++++++++++-------- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 6d4a99f..0602a68 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb - github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191014220836-0f65c00570d0 + github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d diff --git a/go.sum b/go.sum index d2d5b27..e7dc36f 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d h1:FWutTJGVqBn github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d/go.mod h1:Nw3sh5L40Xs1wno7ndbD/dYWg+vARpBvpX9Zz1YSxbo= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77 h1:+UJmKY1f0AITEyfhLaH9zpqIwLgVIKLE+BBCY2B2gys= github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191005213732-3b0dfc9c5f77/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191014220836-0f65c00570d0 h1:4LQSRw7RFYYvriLgOIT5klY3RmtQv//lUodlPwm3CSY= -github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191014220836-0f65c00570d0/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347 h1:bnHpux+c+kROwUL+2nscrUODAa33JQWlViGCBD2W5bg= +github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347/go.mod h1:UUmIu8A7Sjw+yI0tIc/iGQHoSu/7loifZ7E/AbjFFcI= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= github.com/AdamSLevy/sqlite v0.1.3-0.20191014215059-b98bb18889de h1:ehn7bnzDZt3ZTXLR5gn0p0NUUl50wZazoi41y7qNrGY= diff --git a/internal/engine/process.go b/internal/engine/process.go index fda8788..effd9d8 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -274,23 +274,24 @@ func (chain *Chain) revertPending() error { // reflect the state with pending entries applied. Otherwise the chain will // reflect the official state after the most recent EBlock. func (cm *ChainMap) Get(ctx context.Context, - id *factom.Bytes32, pending bool) (Chain, func()) { + id *factom.Bytes32, pending bool) (Chain, func(), error) { chain := cm.get(id) + if !chain.IsTracked() { + return chain, nil, nil + } // Pull a Conn off the Pool and set it as the main Conn. conn := chain.Pool.Get(ctx) if conn == nil { - return Chain{}, nil + return Chain{}, nil, ctx.Err() } chain.Conn = conn // If pending or if there is no pending state, then use the chain as // is, and just return a function that returns the conn to the pool. if pending || chain.Pending.Entries == nil { - return chain, func() { - chain.Pool.Put(conn) - } + return chain, func() { chain.Pool.Put(conn) }, nil } // There are pending entries, but we have been asked for the official // state. @@ -315,5 +316,5 @@ func (cm *ChainMap) Get(ctx context.Context, conn.SetInterrupt(nil) endRead() chain.Pool.Put(conn) - } + }, nil } diff --git a/internal/srv/methods.go b/internal/srv/methods.go index e22ec33..eb8dcc5 100644 --- a/internal/srv/methods.go +++ b/internal/srv/methods.go @@ -223,9 +223,10 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { issuedIDs := engine.Chains.GetIssued() balances := make(api.ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { - chain, put := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) - if put == nil { - return context.Canceled + chain, put, err := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + if err != nil { + // ctx is done + return err } defer put() _, balance, err := addresses.SelectIDBalance(chain.Conn, params.Address) @@ -550,9 +551,14 @@ func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { issuedIDs := engine.Chains.GetIssued() chains := make([]api.ParamsToken, len(issuedIDs)) for i, chainID := range issuedIDs { - chain, put := engine.Chains.Get(ctx, chainID, true) - if put == nil { - return context.Canceled + // Use pending = true because a chain that has a pending + // issuance entry will not show up in this list, and no other + // pending entries will effect the data of interest. Using the + // pending state is more efficient. + chain, put, err := engine.Chains.Get(ctx, chainID, true) + if err != nil { + // ctx is done + return err } defer put() chainID := chainID @@ -603,9 +609,14 @@ func validate(ctx context.Context, chainID := params.ValidChainID() if chainID != nil { ctx, cancel := context.WithTimeout(ctx, 2*time.Second) - chain, put := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + chain, put, err := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + if err != nil { + // ctx is done + return nil, nil, err + } if put == nil { - return nil, nil, context.Canceled + cancel() + return nil, nil, api.ErrorTokenNotFound } if !chain.IsIssued() { cancel() From 506d10c378259c932a292865cb9ddb6ed3697354 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 15 Oct 2019 15:16:52 -0800 Subject: [PATCH 100/124] refactor(engine): Use context.Context.Err() to determine if done --- api/params.go | 6 ++-- internal/engine/engine.go | 62 +++++++++++++++++++-------------------- internal/srv/methods.go | 6 ++-- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/api/params.go b/api/params.go index b29d057..da8e234 100644 --- a/api/params.go +++ b/api/params.go @@ -35,7 +35,7 @@ import ( type Params interface { IsValid() error ValidChainID() *factom.Bytes32 - HasIncludePending() bool + GetIncludePending() bool } // ParamsToken scopes a request down to a single FAT token using either the @@ -71,7 +71,7 @@ func (p ParamsToken) IsValid() error { `required: either "chainid" or both "tokenid" and "issuerid"`) } -func (p ParamsToken) HasIncludePending() bool { return p.IncludePending } +func (p ParamsToken) GetIncludePending() bool { return p.IncludePending } func (p ParamsToken) ValidChainID() *factom.Bytes32 { if p.ChainID != nil { @@ -198,7 +198,7 @@ type ParamsGetBalances struct { IncludePending bool `json:"includepending,omitempty"` } -func (p ParamsGetBalances) HasIncludePending() bool { return p.IncludePending } +func (p ParamsGetBalances) GetIncludePending() bool { return p.IncludePending } func (p ParamsGetBalances) IsValid() error { if p.Address == nil { diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 2b879d9..58ae84a 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -44,14 +44,6 @@ var ( lockFile lockfile.Lockfile ) -func runIfNotDone(ctx context.Context, f func()) { - select { - case <-ctx.Done(): - default: - f() - } -} - const ( scanInterval = 30 * time.Second ) @@ -104,17 +96,17 @@ func Start(ctx context.Context) (done <-chan struct{}) { // Verify Factom Blockchain NetworkID... if err := updateFactomHeight(ctx); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Error(err) - }) + } return } var dblock factom.DBlock dblock.Height = factomHeight if err := dblock.Get(ctx, c); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Errorf("dblock.Get(): %v", err) - }) + } return } if dblock.NetworkID != flag.NetworkID { @@ -129,10 +121,11 @@ func Start(ctx context.Context) (done <-chan struct{}) { log.Warn("Skipping database validation...") } syncHeight, err = loadChains(ctx) + if ctx.Err() != nil { + return + } if err != nil { - runIfNotDone(ctx, func() { - log.Error(err) - }) + log.Error(err) return } // Always close all chain databases if Start fails. @@ -208,7 +201,15 @@ func engine(ctx context.Context, done chan struct{}) { // stopWorkers may be called multiple times by any worker or this // goroutine, but eblocks will only ever be closed once. var once sync.Once - stopWorkers := func() { once.Do(func() { close(eblocks) }) } + stopWorkers := func() { + once.Do(func() { + close(eblocks) + // Drain remaining Eblocks and mark them done. + for range eblocks { + eblocksWG.Done() + } + }) + } // Always stop all workers on exit. defer stopWorkers() @@ -221,14 +222,14 @@ func engine(ctx context.Context, done chan struct{}) { numWorkers := runtime.NumCPU() for i := 0; i < numWorkers; i++ { go func() { + defer stopWorkers() for eb := range eblocks { if err := Process(ctx, dblock.KeyMR, eb); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Errorf("ChainID(%v): %v", eb.ChainID, err) - }) - // Tell workers and engine() to exit. - stopWorkers() + } + return } eblocksWG.Done() } @@ -263,19 +264,18 @@ func engine(ctx context.Context, done chan struct{}) { dblock = factom.DBlock{} dblock.Height = h if err := dblock.Get(ctx, c); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Errorf("%#v.Get(): %v", dblock, err) - }) + } return } - // Queue all EBlocks for processing. + // Queue all EBlocks for processing and wait for all to + // be processed. eblocksWG.Add(len(dblock.EBlocks)) for _, eb := range dblock.EBlocks { eblocks <- eb } - - // Wait for all EBlocks to be processed. eblocksWG.Wait() // Check if any of the workers closed the eblocks @@ -295,9 +295,9 @@ func engine(ctx context.Context, done chan struct{}) { // chains. setSyncHeight(h) if err := Chains.setSync(h, dblock.KeyMR); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Errorf("Chains.setSync(): %v", err) - }) + } return } @@ -317,11 +317,11 @@ func engine(ctx context.Context, done chan struct{}) { // Get and apply any pending entries. var pe factom.PendingEntries if err := pe.Get(ctx, c); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Errorf( "factom.PendingEntries.Get(): %v", err) - }) + } return } @@ -346,10 +346,10 @@ func engine(ctx context.Context, done chan struct{}) { // chain. if err := ProcessPending( ctx, pe[i:j]...); err != nil { - runIfNotDone(ctx, func() { + if ctx.Err() == nil { log.Errorf("ChainID(%v): %v", e.ChainID, err) - }) + } return } } diff --git a/internal/srv/methods.go b/internal/srv/methods.go index eb8dcc5..d23fc14 100644 --- a/internal/srv/methods.go +++ b/internal/srv/methods.go @@ -223,7 +223,7 @@ func getBalances(ctx context.Context, data json.RawMessage) interface{} { issuedIDs := engine.Chains.GetIssued() balances := make(api.ResultGetBalances, len(issuedIDs)) for _, chainID := range issuedIDs { - chain, put, err := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + chain, put, err := engine.Chains.Get(ctx, chainID, params.GetIncludePending()) if err != nil { // ctx is done return err @@ -603,13 +603,13 @@ func validate(ctx context.Context, if err := params.IsValid(); err != nil { return nil, nil, err } - if params.HasIncludePending() && flag.DisablePending { + if params.GetIncludePending() && flag.DisablePending { return nil, nil, api.ErrorPendingDisabled } chainID := params.ValidChainID() if chainID != nil { ctx, cancel := context.WithTimeout(ctx, 2*time.Second) - chain, put, err := engine.Chains.Get(ctx, chainID, params.HasIncludePending()) + chain, put, err := engine.Chains.Get(ctx, chainID, params.GetIncludePending()) if err != nil { // ctx is done return nil, nil, err From 76b007ae585fc5c6d81515a2014b3123fdd3dc07 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 15 Oct 2019 17:10:29 -0800 Subject: [PATCH 101/124] refactor(engine): Simplify pending logic, continue on pe errors, update default db path --- internal/engine/chain.go | 1 - internal/engine/engine.go | 98 ++++++++++++++++++++------------------- internal/flag/flag.go | 11 +++-- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/internal/engine/chain.go b/internal/engine/chain.go index ac133b5..f2883e7 100644 --- a/internal/engine/chain.go +++ b/internal/engine/chain.go @@ -62,7 +62,6 @@ func (chain Chain) String() string { func OpenNew(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) (chain Chain, err error) { - var identity factom.Identity identity.ChainID = new(factom.Bytes32) _, *identity.ChainID = fat.TokenIssuer(eb.Entries[0].ExtIDs) diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 58ae84a..fbaba11 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -204,10 +204,12 @@ func engine(ctx context.Context, done chan struct{}) { stopWorkers := func() { once.Do(func() { close(eblocks) - // Drain remaining Eblocks and mark them done. + // Drain remaining Eblocks and mark them all done. + var n int for range eblocks { - eblocksWG.Done() + n++ } + eblocksWG.Add(-n) }) } @@ -222,7 +224,7 @@ func engine(ctx context.Context, done chan struct{}) { numWorkers := runtime.NumCPU() for i := 0; i < numWorkers; i++ { go func() { - defer stopWorkers() + defer stopWorkers() // If one worker returns, they all return. for eb := range eblocks { if err := Process(ctx, dblock.KeyMR, eb); err != nil { if ctx.Err() == nil { @@ -309,60 +311,62 @@ func engine(ctx context.Context, done chan struct{}) { } } + var pe factom.PendingEntries + // If we aren't yet synced, we want to immediately re-check the // Factom Height as the Blockchain may have advanced in the // time since we started the sync. - if synced { - if !flag.DisablePending { - // Get and apply any pending entries. - var pe factom.PendingEntries - if err := pe.Get(ctx, c); err != nil { - if ctx.Err() == nil { - log.Errorf( - "factom.PendingEntries.Get(): %v", - err) - } - return - } + if !synced { + goto scan + } - for i, j := 0, 0; i < len(pe); i = j { - e := pe[i] - // Unrevealed entries have no ChainID - // and are at the end of the slice. - if e.ChainID == nil { - // No more revealed entries. - break - } - // Grab any subsequent entries with - // this ChainID. - for j = i + 1; j < len(pe); j++ { - chainID := pe[j].ChainID - if chainID == nil || - *chainID != *e.ChainID { - break - } - } - // Process all pending entries for this - // chain. - if err := ProcessPending( - ctx, pe[i:j]...); err != nil { - if ctx.Err() == nil { - log.Errorf("ChainID(%v): %v", - e.ChainID, err) - } - return - } - } + if flag.DisablePending { + goto wait + } + + // Get and apply any pending entries. + if err := pe.Get(ctx, c); err != nil { + if ctx.Err() != nil { + return } + log.Errorf("factom.PendingEntries.Get(): %v", err) + } - // Wait until the next scan tick or we're told to stop. - select { - case <-scanTicker.C: - case <-ctx.Done(): + for i, j := 0, 0; i < len(pe); i = j { + e := pe[i] + // Unrevealed entries have no ChainID + // and are at the end of the slice. + if e.ChainID == nil { + // No more revealed entries. + break + } + // Grab any subsequent entries with + // this ChainID. + for j = i + 1; j < len(pe); j++ { + chainID := pe[j].ChainID + if chainID == nil || *chainID != *e.ChainID { + break + } + } + // Process all pending entries for this + // chain. + if err := ProcessPending(ctx, pe[i:j]...); err != nil { + if ctx.Err() == nil { + log.Errorf("ChainID(%v): %v", + e.ChainID, err) + } return } } + wait: + // Wait until the next scan tick or we're told to stop. + select { + case <-scanTicker.C: + case <-ctx.Done(): + return + } + scan: // Check the Factom blockchain height but log and retry if this // request fails. if err := updateFactomHeight(ctx); err != nil { diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 65cacf0..8ec6a03 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -89,12 +89,13 @@ var ( "disablepending": false, "dbpath": func() string { - dbDir := "/fatd.db" - home, err := os.UserHomeDir() - if err != nil { - return "." + dbDir + if cache, err := os.UserCacheDir(); err == nil { + return cache + "/fatd" + } + if home, err := os.UserHomeDir(); err == nil { + return home + "/.fatd" } - return home + dbDir + return "./fatd.db" }(), "apiaddress": ":8078", From 71df1ad2f17fc3c0ca077643ae919e7fd8fd331c Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 21 Oct 2019 14:17:34 -0800 Subject: [PATCH 102/124] refactor(fat): Clean up APIs and flatten directories Tests still need to be refactored --- api/params.go | 2 +- api/results.go | 2 +- cli/getbalance.go | 4 +- cli/gettransactions.go | 2 +- cli/issue.go | 18 +- cli/transact.go | 31 +-- cli/transactfat0.go | 3 +- cli/transactfat1.go | 3 +- fat/chainid.go | 12 +- fat/chainid_test.go | 85 +++++++ fat/entry.go | 197 ---------------- fat/entry_test.go | 197 ---------------- fat/fat0/transaction.go | 177 -------------- fat/issuance.go | 138 +++++------ fat/issuance_test.go | 115 ++------- fat/type.go | 37 +-- {fat/fat0 => fat0}/addressamountmap.go | 2 +- {fat/fat0 => fat0}/doc.go | 0 fat0/transaction.go | 134 +++++++++++ {fat/fat0 => fat0}/transaction_test.go | 2 +- {fat/fat1 => fat1}/addressnftokensmap.go | 2 +- {fat/fat1 => fat1}/addressnftokensmap_test.go | 0 {fat/fat1 => fat1}/disjointnftokens_test.go | 0 {fat/fat1 => fat1}/nftokenid.go | 2 +- {fat/fat1 => fat1}/nftokenidrange.go | 2 +- {fat/fat1 => fat1}/nftokenmetadata.go | 2 +- {fat/fat1 => fat1}/nftokens.go | 0 {fat/fat1 => fat1}/nftokens_test.go | 0 {fat/fat1 => fat1}/transaction.go | 183 ++++++--------- {fat/fat1 => fat1}/transaction_test.go | 12 +- fat103/sign.go | 50 ++++ fat103/validate.go | 114 +++++++++ fat103/validate_test.go | 218 ++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- internal/db/apply.go | 83 +++---- internal/db/chain.go | 32 +-- internal/db/entries/entries.go | 2 +- internal/db/metadata/metadata.go | 6 +- internal/db/nftokens/nftokens.go | 2 +- internal/db/nftokens/txrelation.go | 2 +- internal/engine/chain.go | 8 +- internal/engine/chainmap.go | 2 +- internal/engine/engine.go | 15 +- internal/engine/process.go | 2 +- {fat => internal}/jsonlen/compact.go | 0 {fat => internal}/jsonlen/compact_test.go | 0 {fat => internal}/jsonlen/number.go | 0 {fat => internal}/jsonlen/number_test.go | 0 internal/srv/methods.go | 108 +++++---- 50 files changed, 956 insertions(+), 1058 deletions(-) create mode 100644 fat/chainid_test.go delete mode 100644 fat/entry.go delete mode 100644 fat/entry_test.go delete mode 100644 fat/fat0/transaction.go rename {fat/fat0 => fat0}/addressamountmap.go (98%) rename {fat/fat0 => fat0}/doc.go (100%) create mode 100644 fat0/transaction.go rename {fat/fat0 => fat0}/transaction_test.go (99%) rename {fat/fat1 => fat1}/addressnftokensmap.go (98%) rename {fat/fat1 => fat1}/addressnftokensmap_test.go (100%) rename {fat/fat1 => fat1}/disjointnftokens_test.go (100%) rename {fat/fat1 => fat1}/nftokenid.go (96%) rename {fat/fat1 => fat1}/nftokenidrange.go (98%) rename {fat/fat1 => fat1}/nftokenmetadata.go (98%) rename {fat/fat1 => fat1}/nftokens.go (100%) rename {fat/fat1 => fat1}/nftokens_test.go (100%) rename {fat/fat1 => fat1}/transaction.go (52%) rename {fat/fat1 => fat1}/transaction_test.go (98%) create mode 100644 fat103/sign.go create mode 100644 fat103/validate.go create mode 100644 fat103/validate_test.go rename {fat => internal}/jsonlen/compact.go (100%) rename {fat => internal}/jsonlen/compact_test.go (100%) rename {fat => internal}/jsonlen/number.go (100%) rename {fat => internal}/jsonlen/number_test.go (100%) diff --git a/api/params.go b/api/params.go index da8e234..fc69ae1 100644 --- a/api/params.go +++ b/api/params.go @@ -29,7 +29,7 @@ import ( jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat1" ) type Params interface { diff --git a/api/results.go b/api/results.go index 50a3861..d2fa884 100644 --- a/api/results.go +++ b/api/results.go @@ -27,7 +27,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat1" ) const APIVersion = "1" diff --git a/cli/getbalance.go b/cli/getbalance.go index 5ef1fa4..d0a2c16 100644 --- a/cli/getbalance.go +++ b/cli/getbalance.go @@ -29,8 +29,8 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/api" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/posener/complete" "github.com/spf13/cobra" ) diff --git a/cli/gettransactions.go b/cli/gettransactions.go index d30b4ad..0271795 100644 --- a/cli/gettransactions.go +++ b/cli/gettransactions.go @@ -31,7 +31,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/api" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/posener/complete" "github.com/spf13/cobra" diff --git a/cli/issue.go b/cli/issue.go index 0fb2195..11d8c4c 100644 --- a/cli/issue.go +++ b/cli/issue.go @@ -198,15 +198,16 @@ func validateIssueFlags(cmd *cobra.Command, args []string) error { } vrbLog.Println("Preparing and signing Token Initialization Entry...") - Issuance.ChainID = paramsToken.ChainID - if err := Issuance.MarshalEntry(); err != nil { + Issuance.Entry.ChainID = paramsToken.ChainID + e, err := Issuance.Sign(sk1) + if err != nil { errLog.Fatal(err) } - Issuance.Sign(sk1) - initCost, err := Issuance.Cost() + initCost, err := e.Cost() if err != nil { errLog.Fatal(err) } + Issuance.Entry = e if !force { vrbLog.Println("Checking chain existence...") @@ -302,7 +303,7 @@ func verifySK1Key(sk1 *factom.SK1Key, idChainID *factom.Bytes32) { errLog.Fatal(err) } vrbLog.Println("Verifying SK1 Key... ") - if *identity.ID1 != sk1.ID1Key() { + if *identity.ID1Key != sk1.ID1Key() { errLog.Fatal( "--sk1 is not the secret key corresponding to the ID1Key declared in the Identity Chain.") } @@ -336,7 +337,7 @@ func issueChain(_ *cobra.Command, _ []string) { } func issueToken(_ *cobra.Command, _ []string) { if curl { - if err := printCurl(Issuance.Entry.Entry, ecEsAdr.Es); err != nil { + if err := printCurl(Issuance.Entry, ecEsAdr.Es); err != nil { errLog.Fatal(err) } return @@ -344,12 +345,13 @@ func issueToken(_ *cobra.Command, _ []string) { vrbLog.Println( "Submitting the Token Initialization Entry to the Factom blockchain...") - txID, err := Issuance.ComposeCreate(context.Background(), FactomClient, ecEsAdr.Es) + txID, err := Issuance.Entry. + ComposeCreate(context.Background(), FactomClient, ecEsAdr.Es) if err != nil { errLog.Fatal(err) } fmt.Println("Token Initialization Entry Submitted") - fmt.Println("Entry Hash: ", Issuance.Hash) + fmt.Println("Entry Hash: ", Issuance.Entry.Hash) fmt.Println("Factom Tx ID:", txID) return } diff --git a/cli/transact.go b/cli/transact.go index e870a52..65259be 100644 --- a/cli/transact.go +++ b/cli/transact.go @@ -28,13 +28,12 @@ import ( "fmt" "math" + jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - - jsonrpc2 "github.com/AdamSLevy/jsonrpc2/v12" + "github.com/Factom-Asset-Tokens/fatd/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/posener/complete" "github.com/spf13/cobra" ) @@ -225,29 +224,27 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { vrbLog.Printf("Preparing %v Transaction Entry...", cmdType) var tx interface { - Sign(...factom.RCDPrivateKey) - MarshalEntry() error - Cost() (uint8, error) + Sign(...factom.RCDPrivateKey) (factom.Entry, error) } switch cmdType { case fat0.Type: - fat0Tx.ChainID = paramsToken.ChainID + fat0Tx.Entry.ChainID = paramsToken.ChainID fat0Tx.Metadata = metadata tx = &fat0Tx case fat1.Type: - fat1Tx.ChainID = paramsToken.ChainID + fat1Tx.Entry.ChainID = paramsToken.ChainID fat1Tx.Metadata = metadata tx = &fat1Tx } - if err := tx.MarshalEntry(); err != nil { + entry, err := tx.Sign(signingSet...) + if err != nil { errLog.Fatal(err) } - vrbLog.Println("Transaction Entry Content: ", tx) - tx.Sign(signingSet...) - cost, err := tx.Cost() + cost, err := entry.Cost() if err != nil { errLog.Fatal(err) } + vrbLog.Println("Transaction Entry Content: ", string(entry.Content)) if !force { vrbLog.Println("Checking token chain status...") @@ -350,14 +347,6 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { vrbLog.Println() } - var entry factom.Entry - switch cmdType { - case fat0.Type: - entry = fat0Tx.Entry.Entry - case fat1.Type: - entry = fat1Tx.Entry.Entry - } - entry.ChainID = paramsToken.ChainID if curl { if err := printCurl(entry, ecEsAdr.Es); err != nil { errLog.Fatal(err) diff --git a/cli/transactfat0.go b/cli/transactfat0.go index 396dde6..234ba21 100644 --- a/cli/transactfat0.go +++ b/cli/transactfat0.go @@ -29,8 +29,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - + "github.com/Factom-Asset-Tokens/fatd/fat0" "github.com/posener/complete" "github.com/spf13/cobra" ) diff --git a/cli/transactfat1.go b/cli/transactfat1.go index 352b2f0..3a82e2d 100644 --- a/cli/transactfat1.go +++ b/cli/transactfat1.go @@ -28,8 +28,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" - + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/posener/complete" "github.com/spf13/cobra" ) diff --git a/fat/chainid.go b/fat/chainid.go index 9c850ae..271a083 100644 --- a/fat/chainid.go +++ b/fat/chainid.go @@ -28,9 +28,9 @@ import ( "github.com/Factom-Asset-Tokens/factom" ) -// ValidTokenNameIDs returns true if the nameIDs match the pattern for a valid -// token chain. -func ValidTokenNameIDs(nameIDs []factom.Bytes) bool { +// ValidNameIDs returns true if the nameIDs match the pattern for a valid token +// chain. +func ValidNameIDs(nameIDs []factom.Bytes) bool { if len(nameIDs) == 4 && len(nameIDs[1]) > 0 && string(nameIDs[0]) == "token" && string(nameIDs[2]) == "issuer" && factom.ValidIdentityChainID(nameIDs[3]) && @@ -53,12 +53,12 @@ func ComputeChainID(tokenID string, issuerChainID *factom.Bytes32) factom.Bytes3 return factom.ComputeChainID(NameIDs(tokenID, issuerChainID)) } -// TokenIssuer returns the tokenID and identityChainID for a given set of +// ParseTokenIssuer returns the tokenID and identityChainID for a given set of // nameIDs. // -// The caller must ensure that ValidTokenNameIDs(nameIDs) returns true or else +// The caller must ensure that ValidNameIDs(nameIDs) returns true or else // TokenIssuer will return garbage data or may panic. -func TokenIssuer(nameIDs []factom.Bytes) (string, factom.Bytes32) { +func ParseTokenIssuer(nameIDs []factom.Bytes) (string, factom.Bytes32) { var identityChainID factom.Bytes32 copy(identityChainID[:], nameIDs[3]) return string(nameIDs[1]), identityChainID diff --git a/fat/chainid_test.go b/fat/chainid_test.go new file mode 100644 index 0000000..14eaf91 --- /dev/null +++ b/fat/chainid_test.go @@ -0,0 +1,85 @@ +package fat + +import ( + "testing" + + "github.com/Factom-Asset-Tokens/factom" + "github.com/stretchr/testify/assert" +) + +var validIdentityChainID = factom.NewBytes32( + "88888807e4f3bbb9a2b229645ab6d2f184224190f83e78761674c2362aca4425") + +func validNameIDs() []factom.Bytes { + return []factom.Bytes{ + factom.Bytes("token"), + factom.Bytes("valid"), + factom.Bytes("issuer"), + validIdentityChainID[:], + } +} + +func TestNameIDs(t *testing.T) { + nameIDs := NameIDs("valid", &validIdentityChainID) + assert.ElementsMatch(t, validNameIDs(), nameIDs) +} +func TestParseTokenIssuer(t *testing.T) { + token, identity := ParseTokenIssuer(validNameIDs()) + assert.Equal(t, "valid", token) + assert.Equal(t, validIdentityChainID, identity) +} + +func TestChainID(t *testing.T) { + expected := factom.NewBytes32( + "b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb") + computed := ComputeChainID("test", &validIdentityChainID) + assert.Equal(t, expected, computed) +} + +func invalidNameIDs(i int) []factom.Bytes { + n := validNameIDs() + n[i] = factom.Bytes{} + return n +} + +var validNameIDsTests = []struct { + Name string + NameIDs []factom.Bytes + Valid bool +}{{ + Name: "valid", + Valid: true, + NameIDs: validNameIDs(), +}, { + Name: "invalid length (short)", + NameIDs: validNameIDs()[0:3], +}, { + Name: "invalid length (long)", + NameIDs: append(validNameIDs()[:], factom.Bytes{}), +}, { + Name: "invalid", + NameIDs: invalidNameIDs(0), +}, { + Name: "invalid ExtID", + NameIDs: invalidNameIDs(1), +}, { + Name: "invalid ExtID", + NameIDs: invalidNameIDs(2), +}, { + Name: "invalid ExtID", + NameIDs: invalidNameIDs(3), +}} + +func TestValidNameIDs(t *testing.T) { + for _, test := range validNameIDsTests { + t.Run(test.Name, func(t *testing.T) { + assert := assert.New(t) + valid := ValidNameIDs(test.NameIDs) + if test.Valid { + assert.True(valid) + } else { + assert.False(valid) + } + }) + } +} diff --git a/fat/entry.go b/fat/entry.go deleted file mode 100644 index 5a74202..0000000 --- a/fat/entry.go +++ /dev/null @@ -1,197 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat - -import ( - "crypto/sha256" - "crypto/sha512" - "encoding/json" - "fmt" - "math/rand" - "strconv" - "time" - - "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" - - "crypto/ed25519" -) - -type Transaction interface { - Validate(*factom.ID1Key) error - IsCoinbase() bool - FactomEntry() factom.Entry - UnmarshalEntry() error -} - -// Entry has variables and methods common to all fat0 entries. -type Entry struct { - Metadata json.RawMessage `json:"metadata,omitempty"` - - factom.Entry `json:"-"` -} - -func (e Entry) FactomEntry() factom.Entry { - return e.Entry -} - -// UnmarshalEntry unmarshals the content of the factom.Entry into the provided -// variable v, disallowing all unknown fields. -func (e Entry) UnmarshalEntry(v interface{}) error { - return json.Unmarshal(e.Content, v) -} - -func (e Entry) MetadataJSONLen() int { - if e.Metadata == nil { - return 0 - } - return len(`,"metadata":`) + len(e.Metadata) -} -func (e *Entry) MarshalEntry(v interface{}) error { - var err error - e.Content, err = json.Marshal(v) - return err -} - -// ValidExtIDs validates the structure of the ExtIDs of the factom.Entry to -// make sure that it has a valid timestamp salt and a valid set of -// RCD/signature pairs. -func (e Entry) ValidExtIDs(numRCDSigPairs int) error { - if numRCDSigPairs == 0 || len(e.ExtIDs) != 2*numRCDSigPairs+1 { - return fmt.Errorf("invalid number of ExtIDs") - } - if err := e.validTimestamp(); err != nil { - return err - } - extIDs := e.ExtIDs[1:] - for i := 0; i < len(extIDs)/2; i++ { - rcd := extIDs[i*2] - if len(rcd) != factom.RCDSize { - return fmt.Errorf("ExtIDs[%v]: invalid RCD size", i+1) - } - if rcd[0] != factom.RCDType { - return fmt.Errorf("ExtIDs[%v]: invalid RCD type", i+1) - } - sig := extIDs[i*2+1] - if len(sig) != factom.SignatureSize { - return fmt.Errorf("ExtIDs[%v]: invalid signature size", i+1) - } - } - return e.validSignatures() -} -func (e Entry) validTimestamp() error { - sec, err := strconv.ParseInt(string(e.ExtIDs[0]), 10, 64) - if err != nil { - return fmt.Errorf("timestamp salt: %w", err) - } - ts := time.Unix(sec, 0) - diff := e.Timestamp.Sub(ts) - if -12*time.Hour > diff || diff > 12*time.Hour { - return fmt.Errorf("timestamp salt expired") - } - return nil -} -func (e Entry) validSignatures() error { - // Compose the signed message data using exactly allocated bytes. - numRcdSigPairs := len(e.ExtIDs) / 2 - maxRcdSigIDSalt := numRcdSigPairs - 1 - maxRcdSigIDSaltStrLen := jsonlen.Uint64(uint64(maxRcdSigIDSalt)) - timeSalt := e.ExtIDs[0] - maxMsgLen := maxRcdSigIDSaltStrLen + len(timeSalt) + len(e.ChainID) + len(e.Content) - msg := make([]byte, maxMsgLen) - i := maxRcdSigIDSaltStrLen - i += copy(msg[i:], timeSalt[:]) - i += copy(msg[i:], e.ChainID[:]) - copy(msg[i:], e.Content) - - rcdSigs := e.ExtIDs[1:] // Skip over timestamp salt in ExtID[0] - for rcdSigID := 0; rcdSigID < numRcdSigPairs; rcdSigID++ { - // Prepend the RCD Sig ID Salt to the message data - rcdSigIDSaltStr := strconv.FormatUint(uint64(rcdSigID), 10) - start := maxRcdSigIDSaltStrLen - len(rcdSigIDSaltStr) - copy(msg[start:], rcdSigIDSaltStr) - - msgHash := sha512.Sum512(msg[start:]) - pubKey := []byte(rcdSigs[rcdSigID*2][1:]) // Omit RCD Type byte - sig := rcdSigs[rcdSigID*2+1] - if !ed25519.Verify(pubKey, msgHash[:], sig) { - return fmt.Errorf("ExtIDs[%v]: invalid signature", rcdSigID*2+2) - } - } - return nil -} - -// Sign the RCD/Sig ID Salt + Timestamp Salt + Chain ID Salt + Content of the -// factom.Entry and add the RCD + signature pairs for the given addresses to -// the ExtIDs. This clears any existing ExtIDs. -func (e *Entry) Sign(signingSet ...factom.RCDPrivateKey) { - // Set the Entry's timestamp so that the signatures will verify against - // this time salt. - timeSalt := newTimestampSalt() - e.Timestamp = time.Now() - - // Compose the signed message data using exactly allocated bytes. - maxRcdSigIDSaltStrLen := jsonlen.Uint64(uint64(len(signingSet))) - maxMsgLen := maxRcdSigIDSaltStrLen + len(timeSalt) + len(e.ChainID) + len(e.Content) - msg := make(factom.Bytes, maxMsgLen) - i := maxRcdSigIDSaltStrLen - i += copy(msg[i:], timeSalt[:]) - i += copy(msg[i:], e.ChainID[:]) - copy(msg[i:], e.Content) - - // Generate the ExtIDs for each address in the signing set. - e.ExtIDs = make([]factom.Bytes, 1, len(signingSet)*2+1) - e.ExtIDs[0] = timeSalt - for rcdSigID, a := range signingSet { - // Compose the RcdSigID salt and prepend it to the message. - rcdSigIDSalt := strconv.FormatUint(uint64(rcdSigID), 10) - start := maxRcdSigIDSaltStrLen - len(rcdSigIDSalt) - copy(msg[start:], rcdSigIDSalt) - - msgHash := sha512.Sum512(msg[start:]) - sig := ed25519.Sign(a.PrivateKey(), msgHash[:]) - e.ExtIDs = append(e.ExtIDs, a.RCD(), sig) - } -} -func newTimestampSalt() []byte { - timestamp := time.Now().Add(time.Duration(-rand.Int63n(int64(1 * time.Hour)))) - return []byte(strconv.FormatInt(timestamp.Unix(), 10)) -} - -// FAAddress computes the FAAddress corresponding to the rcdSigID'th RCD/Sig -// pair. -func (e Entry) FAAddress(rcdSigID int) factom.FAAddress { - id := rcdSigID*2 + 1 - return factom.FAAddress(sha256d(e.ExtIDs[id])) -} - -// ID1Key computes the ID1Key corresponding to the 1st RCD/Sig pair. -func (e Entry) ID1Key() factom.ID1Key { - return factom.ID1Key(e.FAAddress(0)) -} - -// sha256d computes two rounds of the sha256 hash. -func sha256d(data []byte) [sha256.Size]byte { - hash := sha256.Sum256(data) - return sha256.Sum256(hash[:]) -} diff --git a/fat/entry_test.go b/fat/entry_test.go deleted file mode 100644 index 11e54ad..0000000 --- a/fat/entry_test.go +++ /dev/null @@ -1,197 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat_test - -import ( - "math/rand" - "strconv" - "testing" - "time" - - "github.com/Factom-Asset-Tokens/factom" - . "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/stretchr/testify/assert" -) - -var validExtIDsTests = []struct { - Name string - Error string - Entry -}{{ - Name: "valid", - Entry: validEntry(), -}, { - Name: "valid (large signing set)", - Entry: func() Entry { - e := validEntry() - adrs := make([]factom.RCDPrivateKey, 100) - for i := range adrs { - adr, _ := factom.GenerateFsAddress() - adrs[i] = adr - } - e.Sign(adrs...) - return e - }(), -}, { - Name: "nil ExtIDs", - Error: "invalid number of ExtIDs", - Entry: func() Entry { - e := validEntry() - e.ExtIDs = nil - return e - }(), -}, { - Name: "extra ExtIDs", - Error: "invalid number of ExtIDs", - Entry: func() Entry { - e := validEntry() - e.ExtIDs = append(e.ExtIDs, factom.Bytes{}) - return e - }(), -}, { - Name: "invalid timestamp (format)", - Error: "timestamp salt: strconv.ParseInt: parsing \"xxxx\": invalid syntax", - Entry: func() Entry { - e := validEntry() - e.ExtIDs[0] = []byte("xxxx") - return e - }(), -}, { - Name: "invalid timestamp (expired)", - Error: "timestamp salt expired", - Entry: func() Entry { - e := validEntry() - e.Timestamp = time.Now().Add(-48 * time.Hour) - return e - }(), -}, { - Name: "invalid timestamp (expired)", - Error: "timestamp salt expired", - Entry: func() Entry { - e := validEntry() - e.Timestamp = time.Now().Add(48 * time.Hour) - return e - }(), -}, { - Name: "invalid RCD size", - Error: "ExtIDs[1]: invalid RCD size", - Entry: func() Entry { - e := validEntry() - e.ExtIDs[1] = append(e.ExtIDs[1], 0x00) - return e - }(), -}, { - Name: "invalid RCD type", - Error: "ExtIDs[1]: invalid RCD type", - Entry: func() Entry { - e := validEntry() - e.ExtIDs[1][0]++ - return e - }(), -}, { - Name: "invalid signature size", - Entry: func() Entry { - e := validEntry() - e.ExtIDs[2] = append(e.ExtIDs[2], 0x00) - return e - }(), - Error: "ExtIDs[1]: invalid signature size", -}, { - Name: "invalid signatures", - Entry: func() Entry { - e := validEntry() - e.ExtIDs[2][0]++ - return e - }(), - Error: "ExtIDs[2]: invalid signature", -}, { - Name: "invalid signatures (transpose)", - Entry: func() Entry { - e := validEntry() - rcdSig := e.ExtIDs[1:3] - e.ExtIDs[1] = e.ExtIDs[3] - e.ExtIDs[2] = e.ExtIDs[4] - e.ExtIDs[3] = rcdSig[0] - e.ExtIDs[4] = rcdSig[1] - return e - }(), - Error: "ExtIDs[2]: invalid signature", -}, { - Name: "invalid signatures (timestamp)", - Entry: func() Entry { - e := validEntry() - ts := time.Now().Add(time.Duration( - -rand.Int63n(int64(12 * time.Hour)))) - timeSalt := []byte(strconv.FormatInt(ts.Unix(), 10)) - e.ExtIDs[0] = timeSalt - return e - }(), - Error: "ExtIDs[2]: invalid signature", -}, { - Name: "invalid signatures (chain ID)", - Entry: func() Entry { - e := validEntry() - e.ChainID = factom.NewBytes32(factom.Bytes{0x01, 0x02}) - return e - }(), - Error: "ExtIDs[2]: invalid signature", -}, -} - -func TestEntryValidExtIDs(t *testing.T) { - for _, test := range validExtIDsTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - err := test.Entry.ValidExtIDs(len(test.Entry.ExtIDs) / 2) - if len(test.Error) == 0 { - assert.NoError(err) - } else { - assert.EqualError(err, test.Error) - } - }) - } -} - -var randSource = rand.New(rand.NewSource(100)) - -func validEntry() Entry { - var e Entry - e.Content = factom.Bytes{0x00, 0x01, 0x02} - e.ChainID = factom.NewBytes32(nil) - // Generate valid signatures with blank Addresses. - adrs := twoAddresses() - e.Sign(adrs[0], adrs[1]) - return e -} - -func twoAddresses() []factom.FsAddress { - adrs := make([]factom.FsAddress, 2) - for i := range adrs { - adr, err := factom.GenerateFsAddress() - if err != nil { - panic(err) - } - adrs[i] = adr - } - return adrs -} diff --git a/fat/fat0/transaction.go b/fat/fat0/transaction.go deleted file mode 100644 index cc41719..0000000 --- a/fat/fat0/transaction.go +++ /dev/null @@ -1,177 +0,0 @@ -// MIT License -// -// Copyright 2018 Canonical Ledgers, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -package fat0 - -import ( - "encoding/json" - "fmt" - - "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" -) - -const Type = fat.TypeFAT0 - -// Transaction represents a fat0 transaction, which can be a normal account -// transaction or a coinbase transaction depending on the Inputs and the -// RCD/signature pair. -type Transaction struct { - Inputs AddressAmountMap `json:"inputs"` - Outputs AddressAmountMap `json:"outputs"` - fat.Entry -} - -var _ fat.Transaction = &Transaction{} - -// NewTransaction returns a Transaction initialized with the given entry. -func NewTransaction(entry factom.Entry) *Transaction { - return &Transaction{Entry: fat.Entry{Entry: entry}} -} - -func (t *Transaction) UnmarshalJSON(data []byte) error { - data = jsonlen.Compact(data) - tRaw := struct { - Inputs json.RawMessage `json:"inputs"` - Outputs json.RawMessage `json:"outputs"` - fat.Entry - }{} - if err := json.Unmarshal(data, &tRaw); err != nil { - return fmt.Errorf("%T: %w", t, err) - } - if err := t.Inputs.UnmarshalJSON(tRaw.Inputs); err != nil { - return fmt.Errorf("%T.Inputs: %w", t, err) - } - if err := t.Outputs.UnmarshalJSON(tRaw.Outputs); err != nil { - return fmt.Errorf("%T.Outputs: %w", t, err) - } - t.Metadata = tRaw.Metadata - - if err := t.ValidData(); err != nil { - return fmt.Errorf("%T: %w", t, err) - } - - expectedJSONLen := len(`{"inputs":,"outputs":}`) + - len(tRaw.Inputs) + len(tRaw.Outputs) + - tRaw.MetadataJSONLen() - if expectedJSONLen != len(data) { - return fmt.Errorf("%T: unexpected JSON length", t) - } - - return nil -} - -type transaction Transaction - -func (t Transaction) MarshalJSON() ([]byte, error) { - if err := t.ValidData(); err != nil { - return nil, err - } - return json.Marshal(transaction(t)) -} - -func (t Transaction) String() string { - data, err := t.MarshalJSON() - if err != nil { - return err.Error() - } - return string(data) -} - -// UnmarshalEntry unmarshals the entry content as a Transaction. -func (t *Transaction) UnmarshalEntry() error { - return t.Entry.UnmarshalEntry(t) -} - -// MarshalEntry marshals the Transaction into the Entry content. -func (t *Transaction) MarshalEntry() error { - return t.Entry.MarshalEntry(t) -} - -// IsCoinbase returns true if the coinbase address is in t.Input. This does not -// necessarily mean that t is a valid coinbase transaction. -func (t Transaction) IsCoinbase() bool { - amount := t.Inputs[fat.Coinbase()] - return amount != 0 -} - -// Validate performs all validation checks and returns nil if t is a valid -// Transaction. If t is a coinbase transaction then idKey is used to validate -// the RCD. Otherwise RCDs are checked against the input addresses. -func (t *Transaction) Validate(idKey *factom.ID1Key) error { - if err := t.UnmarshalEntry(); err != nil { - return err - } - if err := t.ValidExtIDs(); err != nil { - return err - } - if t.IsCoinbase() { - if t.ID1Key() != *idKey { - return fmt.Errorf("invalid RCD") - } - } else { - if !t.ValidRCDs() { - return fmt.Errorf("invalid RCDs") - } - } - return nil -} - -// ValidData validates the Transaction data and returns nil if no errors are -// present. ValidData assumes that the entry content has been unmarshaled. -func (t Transaction) ValidData() error { - if t.Inputs.Sum() != t.Outputs.Sum() { - return fmt.Errorf("sum(inputs) != sum(outputs)") - } - // Coinbase transactions must only have one input. - if t.IsCoinbase() && len(t.Inputs) != 1 { - return fmt.Errorf("invalid coinbase transaction") - } - return nil -} - -// ValidExtIDs validates the structure of the external IDs of the entry to make -// sure that it has the correct number of RCD/signature pairs. ValidExtIDs does -// not validate the content of the RCD or signature. ValidExtIDs assumes that -// the entry content has been unmarshaled and that ValidData returns nil. -func (t Transaction) ValidExtIDs() error { - return t.Entry.ValidExtIDs(len(t.Inputs)) -} - -func (t Transaction) ValidRCDs() bool { - // Create a map of all RCDs that are present in the ExtIDs. - rcdHashes := make(map[factom.FAAddress]struct{}, len(t.Inputs)) - extIDs := t.ExtIDs[1:] - for i := 0; i < len(extIDs)/2; i++ { - rcdHashes[t.FAAddress(i)] = struct{}{} - } - - // Ensure that for all Inputs there is a corresponding RCD in the - // ExtIDs. - for rcdHash := range t.Inputs { - if _, ok := rcdHashes[rcdHash]; !ok { - return false - } - } - return true -} diff --git a/fat/issuance.go b/fat/issuance.go index 49ec1db..35309af 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -27,12 +27,11 @@ import ( "fmt" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/fat103" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) -var ( - coinbase = factom.FsAddress{}.FAAddress() -) +var coinbase = factom.FsAddress{}.FAAddress() func Coinbase() factom.FAAddress { return coinbase @@ -40,24 +39,67 @@ func Coinbase() factom.FAAddress { const MaxPrecision = 18 -// Issuance represents the Issuance of a token. type Issuance struct { Type Type `json:"type"` Supply int64 `json:"supply"` Precision uint `json:"precision,omitempty"` Symbol string `json:"symbol,omitempty"` - Entry + + Metadata json.RawMessage `json:"metadata,omitempty"` + + Entry factom.Entry `json:"-"` +} + +func NewIssuance(e factom.Entry, idKey *factom.Bytes32) (Issuance, error) { + var i Issuance + if err := i.UnmarshalJSON(e.Content); err != nil { + return i, err + } + + if i.Supply == 0 || i.Supply < -1 { + return i, fmt.Errorf(`invalid "supply": must be positive or -1`) + } + + switch i.Type { + case TypeFAT0: + if i.Precision > MaxPrecision { + return i, fmt.Errorf( + `invalid "precision": must be less than 18`) + } + case TypeFAT1: + if i.Precision != 0 { + return i, fmt.Errorf( + `invalid "precision": not allowed for %v`, i.Type) + } + default: + return i, fmt.Errorf(`invalid "type": %v`, i.Type) + } + + expected := map[factom.Bytes32]struct{}{*idKey: struct{}{}} + if err := fat103.Validate(e, expected); err != nil { + return i, err + } + + i.Entry = e + + return i, nil } -type issuance Issuance +func (i Issuance) Sign(idKey factom.RCDPrivateKey) (factom.Entry, error) { + e := i.Entry + content, err := json.Marshal(i) + if err != nil { + return e, err + } + e.Content = content + return fat103.Sign(e, idKey), nil +} func (i *Issuance) UnmarshalJSON(data []byte) error { data = jsonlen.Compact(data) - if err := json.Unmarshal(data, (*issuance)(i)); err != nil { - return fmt.Errorf("%T: %w", i, err) - } - if err := i.ValidData(); err != nil { + type _i Issuance + if err := json.Unmarshal(data, (*_i)(i)); err != nil { return fmt.Errorf("%T: %w", i, err) } if i.expectedJSONLength() != len(data) { @@ -75,76 +117,8 @@ func (i Issuance) expectedJSONLength() int { if len(i.Symbol) > 0 { l += len(`,"symbol":""`) + len(i.Symbol) } - l += i.MetadataJSONLen() - return l -} - -func (i Issuance) MarshalJSON() ([]byte, error) { - if err := i.ValidData(); err != nil { - return nil, err - } - return json.Marshal(issuance(i)) -} - -// NewIssuance returns an Issuance initialized with the given entry. -func NewIssuance(entry factom.Entry) Issuance { - return Issuance{Entry: Entry{Entry: entry}} -} - -// UnmarshalEntry unmarshals the entry content as an Issuance. -func (i *Issuance) UnmarshalEntry() error { - return i.Entry.UnmarshalEntry(i) -} - -// MarshalEntry marshals the entry content as an Issuance. -func (i *Issuance) MarshalEntry() error { - return i.Entry.MarshalEntry(i) -} - -// Validate performs all validation checks and returns nil if i is a valid -// Issuance. -func (i *Issuance) Validate(idKey *factom.ID1Key) error { - if err := i.UnmarshalEntry(); err != nil { - return err - } - if err := i.ValidExtIDs(); err != nil { - return err - } - if i.ID1Key() != *idKey { - return fmt.Errorf("invalid RCD") - } - return nil -} - -// ValidData validates the Issuance data and returns nil if no errors are -// present. ValidData assumes that the entry content has been unmarshaled. -func (i Issuance) ValidData() error { - if !i.Type.IsValid() { - return fmt.Errorf(`invalid "type": %v`, i.Type) - } - if i.Supply == 0 || i.Supply < -1 { - return fmt.Errorf(`invalid "supply": must be positive or -1`) - } - switch i.Type { - case TypeFAT0: - if i.Precision > MaxPrecision { - return fmt.Errorf( - `invalid "precision": must be less than 18`) - } - case TypeFAT1: - if i.Precision != 0 { - return fmt.Errorf( - `invalid "precision": not allowed for %v`, i.Type) - } - default: - panic(i.Type.String()) + if i.Metadata != nil { + l += len(`,"metadata":`) + len(i.Metadata) } - return nil -} - -// ValidExtIDs validates the structure of the external IDs of the entry to make -// sure that it has an RCD and signature. It does not validate the content of -// the RCD or signature. -func (i Issuance) ValidExtIDs() error { - return i.Entry.ValidExtIDs(1) + return l } diff --git a/fat/issuance_test.go b/fat/issuance_test.go index 514545e..bf69c54 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -20,105 +20,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -package fat_test +package fat import ( - "encoding/hex" "encoding/json" "testing" "github.com/Factom-Asset-Tokens/factom" - . "github.com/Factom-Asset-Tokens/fatd/fat" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -var humanReadableZeroAddress = "FA1zT4aFpEvcnPqPCigB3fvGu4Q4mTXY22iiuV69DqE1pNhdF2MC" - -var validIdentityChainIDStr = "88888807e4f3bbb9a2b229645ab6d2f184224190f83e78761674c2362aca4425" - -func validIdentityChainID() factom.Bytes { - return hexToBytes(validIdentityChainIDStr) -} - -func hexToBytes(hexStr string) factom.Bytes { - raw, err := hex.DecodeString(hexStr) - if err != nil { - panic(err) - } - return factom.Bytes(raw) -} +var coinbaseAddressStr = "FA1zT4aFpEvcnPqPCigB3fvGu4Q4mTXY22iiuV69DqE1pNhdF2MC" func TestCoinbase(t *testing.T) { a := Coinbase() - require := require.New(t) - require.Equal(humanReadableZeroAddress, a.String()) -} - -var ( - identityChainID = factom.NewBytes32(validIdentityChainID()) -) - -func TestChainID(t *testing.T) { - assert.Equal(t, "b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb", - ComputeChainID("test", identityChainID).String()) -} - -var validTokenNameIDsTests = []struct { - Name string - NameIDs []factom.Bytes - Valid bool -}{{ - Name: "valid", - Valid: true, - NameIDs: validTokenNameIDs(), -}, { - Name: "invalid length (short)", - NameIDs: validTokenNameIDs()[0:3], -}, { - Name: "invalid length (long)", - NameIDs: append(validTokenNameIDs(), factom.Bytes{}), -}, { - Name: "invalid ExtID", - NameIDs: invalidTokenNameIDs(0), -}, { - Name: "invalid ExtID", - NameIDs: invalidTokenNameIDs(1), -}, { - Name: "invalid ExtID", - NameIDs: invalidTokenNameIDs(2), -}, { - Name: "invalid ExtID", - NameIDs: invalidTokenNameIDs(3), -}} - -func TestValidTokenNameIDs(t *testing.T) { - for _, test := range validTokenNameIDsTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - valid := ValidTokenNameIDs(test.NameIDs) - if test.Valid { - assert.True(valid) - } else { - assert.False(valid) - } - }) - } -} - -func validTokenNameIDs() []factom.Bytes { - return []factom.Bytes{ - factom.Bytes("token"), - factom.Bytes("valid"), - factom.Bytes("issuer"), - identityChainID[:], - } -} - -func invalidTokenNameIDs(i int) []factom.Bytes { - n := validTokenNameIDs() - n[i] = factom.Bytes{} - return n + assert.Equal(t, coinbaseAddressStr, a.String()) } var issuanceTests = []struct { @@ -166,7 +82,7 @@ var issuanceTests = []struct { Name: "invalid JSON (nil)", Error: `unexpected end of JSON input`, IssuerKey: issuerKey, - Issuance: issuance(nil), + Issuance: issuanceFromRaw(nil), }, { Name: "invalid data (type)", Error: `*fat.Issuance: *fat.Type: invalid format`, @@ -222,7 +138,7 @@ func TestIssuance(t *testing.T) { assert := assert.New(t) i := test.Issuance key := test.IssuerKey - err := i.Validate(&key) + err := i.Validate((*factom.Bytes32)(&key)) if len(test.Error) == 0 { assert.NoError(err) } else { @@ -242,7 +158,7 @@ func validIssuanceEntryContentMap() map[string]interface{} { } func validIssuance() Issuance { - return issuance(marshal(validIssuanceEntryContentMap())) + return issuanceFromRaw(marshal(validIssuanceEntryContentMap())) } var issuerSecret = func() factom.SK1Key { @@ -251,13 +167,14 @@ var issuerSecret = func() factom.SK1Key { }() var issuerKey = issuerSecret.ID1Key() -func issuance(content factom.Bytes) Issuance { - e := factom.Entry{ - ChainID: factom.NewBytes32(nil), +func issuanceFromRaw(content factom.Bytes) Issuance { + e := Entry{factom.Entry{ + ChainID: new(factom.Bytes32), Content: content, - } - i := NewIssuance(e) - i.Sign(issuerSecret) + }} + e.Sign(issuerSecret) + id1Key := issuerSecret.ID1Key() + i, _ := NewIssuance(e.Entry, (*factom.Bytes32)(&id1Key)) return i } @@ -268,13 +185,13 @@ func invalidIssuance(field string) Issuance { func omitFieldIssuance(field string) Issuance { m := validIssuanceEntryContentMap() delete(m, field) - return issuance(marshal(m)) + return issuanceFromRaw(marshal(m)) } func setFieldIssuance(field string, value interface{}) Issuance { m := validIssuanceEntryContentMap() m[field] = value - return issuance(marshal(m)) + return issuanceFromRaw(marshal(m)) } func marshal(v map[string]interface{}) []byte { @@ -322,7 +239,7 @@ func TestIssuanceMarshalEntry(t *testing.T) { t.Run(test.Name, func(t *testing.T) { assert := assert.New(t) i := test.Issuance - err := i.MarshalEntry() + err := i.PopulateEntry(&issuerSecret) if len(test.Error) == 0 { assert.NoError(err) } else { diff --git a/fat/type.go b/fat/type.go index c7dbb0b..31f9f56 100644 --- a/fat/type.go +++ b/fat/type.go @@ -27,7 +27,7 @@ import ( "strconv" ) -type Type uint64 +type Type uint const ( TypeFAT0 Type = iota @@ -35,36 +35,37 @@ const ( ) func (t *Type) Set(s string) error { - format := s[0:len(`FAT-`)] - if format != `FAT-` { + return t.UnmarshalText([]byte(s)) +} + +const format = `FAT-` + +func (t *Type) UnmarshalText(text []byte) error { + if string(text[0:len(format)]) != format { return fmt.Errorf("%T: invalid format", t) } - num := s[len(format):] - var err error - if *(*uint64)(t), err = strconv.ParseUint(num, 10, 64); err != nil { + i, err := strconv.ParseUint(string(text[len(format):]), 10, 64) + if err != nil { return fmt.Errorf("%T: %w", t, err) } + *t = Type(i) return nil } -func (t *Type) UnmarshalJSON(data []byte) error { - if data[0] != '"' || data[len(data)-1] != '"' { - return fmt.Errorf("%T: expected JSON string", t) +func (t Type) MarshalText() ([]byte, error) { + if !t.IsValid() { + return nil, fmt.Errorf("invalid fat.Type: %v", int(t)) } - data = data[1 : len(data)-1] - return t.Set(string(data)) -} -func (t Type) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf("%#v", t.String())), nil + return []byte(fmt.Sprintf("%v%v", format, int(t))), nil } func (t Type) String() string { - fmtStr := "FAT-%v" - if !t.IsValid() { - fmtStr = "invalid fat.Type: %v" + text, err := t.MarshalText() + if err != nil { + return err.Error() } - return fmt.Sprintf(fmtStr, uint64(t)) + return string(text) } func (t Type) IsValid() bool { diff --git a/fat/fat0/addressamountmap.go b/fat0/addressamountmap.go similarity index 98% rename from fat/fat0/addressamountmap.go rename to fat0/addressamountmap.go index e350857..b85dde9 100644 --- a/fat/fat0/addressamountmap.go +++ b/fat0/addressamountmap.go @@ -27,7 +27,7 @@ import ( "fmt" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) // AddressAmountMap relates a factom.FAAddress to its amount for the Inputs and diff --git a/fat/fat0/doc.go b/fat0/doc.go similarity index 100% rename from fat/fat0/doc.go rename to fat0/doc.go diff --git a/fat0/transaction.go b/fat0/transaction.go new file mode 100644 index 0000000..3367793 --- /dev/null +++ b/fat0/transaction.go @@ -0,0 +1,134 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package fat0 + +import ( + "encoding/json" + "fmt" + + "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/fat103" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" +) + +const Type = fat.TypeFAT0 + +// Transaction represents a fat0 transaction, which can be a normal account +// transaction or a coinbase transaction depending on the Inputs and the +// RCD/signature pair. +type Transaction struct { + Inputs AddressAmountMap `json:"inputs"` + Outputs AddressAmountMap `json:"outputs"` + + Metadata json.RawMessage `json:"metadata,omitempty"` + + Entry factom.Entry `json:"-"` +} + +func NewTransaction(e factom.Entry, idKey *factom.Bytes32) (Transaction, error) { + var t Transaction + if err := t.UnmarshalJSON(e.Content); err != nil { + return t, err + } + + if t.Inputs.Sum() != t.Outputs.Sum() { + return t, fmt.Errorf("sum(inputs) != sum(outputs)") + } + + var expected map[factom.Bytes32]struct{} + // Coinbase transactions must only have one input. + if t.IsCoinbase() { + if len(t.Inputs) != 1 { + return t, fmt.Errorf("invalid coinbase transaction") + } + + expected = map[factom.Bytes32]struct{}{*idKey: struct{}{}} + } else { + expected = make(map[factom.Bytes32]struct{}, len(t.Inputs)) + for adr := range t.Inputs { + expected[factom.Bytes32(adr)] = struct{}{} + } + } + + if err := fat103.Validate(e, expected); err != nil { + return t, err + } + + t.Entry = e + + return t, nil +} + +func (t *Transaction) UnmarshalJSON(data []byte) error { + data = jsonlen.Compact(data) + var tRaw struct { + Inputs json.RawMessage `json:"inputs"` + Outputs json.RawMessage `json:"outputs"` + Metadata json.RawMessage `json:"metadata,omitempty"` + } + if err := json.Unmarshal(data, &tRaw); err != nil { + return fmt.Errorf("%T: %w", t, err) + } + if err := t.Inputs.UnmarshalJSON(tRaw.Inputs); err != nil { + return fmt.Errorf("%T.Inputs: %w", t, err) + } + if err := t.Outputs.UnmarshalJSON(tRaw.Outputs); err != nil { + return fmt.Errorf("%T.Outputs: %w", t, err) + } + t.Metadata = tRaw.Metadata + + expectedJSONLen := len(`{"inputs":,"outputs":}`) + + len(tRaw.Inputs) + len(tRaw.Outputs) + if tRaw.Metadata != nil { + expectedJSONLen += len(`,"metadata":`) + len(tRaw.Metadata) + } + if expectedJSONLen != len(data) { + return fmt.Errorf("%T: unexpected JSON length", t) + } + + return nil +} + +func (t Transaction) IsCoinbase() bool { + _, ok := t.Inputs[fat.Coinbase()] + return ok +} + +func (t Transaction) String() string { + data, err := json.Marshal(t) + if err != nil { + return err.Error() + } + return string(data) +} + +func (t Transaction) Sign(signingSet ...factom.RCDPrivateKey) (factom.Entry, error) { + e := t.Entry + content, err := json.Marshal(t) + if err != nil { + return e, err + } + e.Content = content + return fat103.Sign(e, signingSet...), nil +} diff --git a/fat/fat0/transaction_test.go b/fat0/transaction_test.go similarity index 99% rename from fat/fat0/transaction_test.go rename to fat0/transaction_test.go index 7d07d32..8fc8769 100644 --- a/fat/fat0/transaction_test.go +++ b/fat0/transaction_test.go @@ -30,7 +30,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - . "github.com/Factom-Asset-Tokens/fatd/fat/fat0" + . "github.com/Factom-Asset-Tokens/fatd/fat0" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/fat/fat1/addressnftokensmap.go b/fat1/addressnftokensmap.go similarity index 98% rename from fat/fat1/addressnftokensmap.go rename to fat1/addressnftokensmap.go index a7abb74..f24d5bc 100644 --- a/fat/fat1/addressnftokensmap.go +++ b/fat1/addressnftokensmap.go @@ -27,7 +27,7 @@ import ( "fmt" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) // AddressTokenMap relates the RCDHash of an address to its NFTokenIDs. diff --git a/fat/fat1/addressnftokensmap_test.go b/fat1/addressnftokensmap_test.go similarity index 100% rename from fat/fat1/addressnftokensmap_test.go rename to fat1/addressnftokensmap_test.go diff --git a/fat/fat1/disjointnftokens_test.go b/fat1/disjointnftokens_test.go similarity index 100% rename from fat/fat1/disjointnftokens_test.go rename to fat1/disjointnftokens_test.go diff --git a/fat/fat1/nftokenid.go b/fat1/nftokenid.go similarity index 96% rename from fat/fat1/nftokenid.go rename to fat1/nftokenid.go index f319b0a..73e115e 100644 --- a/fat/fat1/nftokenid.go +++ b/fat1/nftokenid.go @@ -25,7 +25,7 @@ package fat1 import ( "fmt" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) // NFTokenID is a Non-Fungible Token ID. diff --git a/fat/fat1/nftokenidrange.go b/fat1/nftokenidrange.go similarity index 98% rename from fat/fat1/nftokenidrange.go rename to fat1/nftokenidrange.go index 6dec135..e506f04 100644 --- a/fat/fat1/nftokenidrange.go +++ b/fat1/nftokenidrange.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) // NFTokenIDRange represents a contiguous range of NFTokenIDs. diff --git a/fat/fat1/nftokenmetadata.go b/fat1/nftokenmetadata.go similarity index 98% rename from fat/fat1/nftokenmetadata.go rename to fat1/nftokenmetadata.go index afb03d8..7c9799f 100644 --- a/fat/fat1/nftokenmetadata.go +++ b/fat1/nftokenmetadata.go @@ -26,7 +26,7 @@ import ( "encoding/json" "fmt" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) type NFTokenIDMetadataMap map[NFTokenID]json.RawMessage diff --git a/fat/fat1/nftokens.go b/fat1/nftokens.go similarity index 100% rename from fat/fat1/nftokens.go rename to fat1/nftokens.go diff --git a/fat/fat1/nftokens_test.go b/fat1/nftokens_test.go similarity index 100% rename from fat/fat1/nftokens_test.go rename to fat1/nftokens_test.go diff --git a/fat/fat1/transaction.go b/fat1/transaction.go similarity index 52% rename from fat/fat1/transaction.go rename to fat1/transaction.go index 31bd397..e789bf8 100644 --- a/fat/fat1/transaction.go +++ b/fat1/transaction.go @@ -28,7 +28,8 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/jsonlen" + "github.com/Factom-Asset-Tokens/fatd/fat103" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) const Type = fat.TypeFAT1 @@ -40,14 +41,56 @@ type Transaction struct { Inputs AddressNFTokensMap `json:"inputs"` Outputs AddressNFTokensMap `json:"outputs"` TokenMetadata NFTokenIDMetadataMap `json:"tokenmetadata,omitempty"` - fat.Entry + + Metadata json.RawMessage `json:"metadata,omitempty"` + + Entry factom.Entry `json:"-"` } -var _ fat.Transaction = &Transaction{} +func NewTransaction(e factom.Entry, idKey *factom.Bytes32) (Transaction, error) { + var t Transaction + if err := t.UnmarshalJSON(e.Content); err != nil { + return t, err + } + + if err := t.Inputs.NoAddressIntersection(t.Outputs); err != nil { + return t, fmt.Errorf("Inputs and Outputs intersect: %w", err) + } + if err := t.Inputs.NFTokenIDsConserved(t.Outputs); err != nil { + return t, fmt.Errorf("Inputs and Outputs mismatch: %w", err) + } + + var expected map[factom.Bytes32]struct{} + // Coinbase transactions must only have one input. + if t.IsCoinbase() { + if len(t.Inputs) != 1 { + return t, fmt.Errorf("invalid coinbase transaction") + } + if err := t.TokenMetadata.IsSubsetOf( + t.Inputs[fat.Coinbase()]); err != nil { + return t, fmt.Errorf("%T.TokenMetadata: %w", t, err) + } + + expected = map[factom.Bytes32]struct{}{*idKey: struct{}{}} + } else { + if len(t.TokenMetadata) > 0 { + return t, fmt.Errorf( + `non-coinbase transaction with "tokenmetadata"`) + } + + expected = make(map[factom.Bytes32]struct{}, len(t.Inputs)) + for adr := range t.Inputs { + expected[factom.Bytes32(adr)] = struct{}{} + } + } + + if err := fat103.Validate(e, expected); err != nil { + return t, err + } + + t.Entry = e -// NewTransaction returns a Transaction initialized with the given entry. -func NewTransaction(entry factom.Entry) *Transaction { - return &Transaction{Entry: fat.Entry{Entry: entry}} + return t, nil } func (t *Transaction) UnmarshalJSON(data []byte) error { @@ -55,7 +98,7 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { Inputs json.RawMessage `json:"inputs"` Outputs json.RawMessage `json:"outputs"` TokenMetadata json.RawMessage `json:"tokenmetadata"` - fat.Entry + Metadata json.RawMessage `json:"metadata,omitempty"` }{} if err := json.Unmarshal(data, &tRaw); err != nil { return fmt.Errorf("%T: %w", t, err) @@ -63,41 +106,27 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { if err := t.Inputs.UnmarshalJSON(tRaw.Inputs); err != nil { return fmt.Errorf("%T.Inputs: %w", t, err) } - var expectedJSONLen int + if err := t.Outputs.UnmarshalJSON(tRaw.Outputs); err != nil { + return fmt.Errorf("%T.Outputs: %w", t, err) + } + + expectedJSONLen := len(`{"inputs":,"outputs":}`) + + len(jsonlen.Compact(tRaw.Inputs)) + len(jsonlen.Compact(tRaw.Outputs)) + if tRaw.Metadata != nil { + expectedJSONLen += len(`,"metadata":`) + len(tRaw.Metadata) + t.Metadata = tRaw.Metadata + } + if len(tRaw.TokenMetadata) > 0 { - if !t.IsCoinbase() { - return fmt.Errorf(`%T: %v`, t, - `invalid field for non-coinbase transaction: "tokenmetadata"`) - } if err := t.TokenMetadata.UnmarshalJSON(tRaw.TokenMetadata); err != nil { return fmt.Errorf("%T.TokenMetadata: %w", t, err) } - if err := t.TokenMetadata.IsSubsetOf(t.Inputs[fat.Coinbase()]); err != nil { - return fmt.Errorf("%T.TokenMetadata: %w", t, err) - } - expectedJSONLen = len(`,"tokenmetadata":`) + + expectedJSONLen += len(`,"tokenmetadata":`) + len(jsonlen.Compact(tRaw.TokenMetadata)) - } else { - if t.IsCoinbase() { - // Avoid a nil map. - t.TokenMetadata = make(NFTokenIDMetadataMap, 0) - } } - if err := t.Outputs.UnmarshalJSON(tRaw.Outputs); err != nil { - return fmt.Errorf("%T.Outputs: %w", t, err) - } - t.Metadata = tRaw.Metadata - - if err := t.ValidData(); err != nil { - return fmt.Errorf("%T: %w", t, err) - } - - expectedJSONLen += len(`{"inputs":,"outputs":}`) + - len(jsonlen.Compact(tRaw.Inputs)) + len(jsonlen.Compact(tRaw.Outputs)) + - tRaw.MetadataJSONLen() if expectedJSONLen != len(jsonlen.Compact(data)) { return fmt.Errorf("%T: unexpected JSON length", t) } @@ -105,91 +134,25 @@ func (t *Transaction) UnmarshalJSON(data []byte) error { return nil } -type transaction Transaction - -func (t Transaction) MarshalJSON() ([]byte, error) { - if err := t.ValidData(); err != nil { - return nil, err - } - return json.Marshal(transaction(t)) +func (t Transaction) IsCoinbase() bool { + _, ok := t.Inputs[fat.Coinbase()] + return ok } func (t Transaction) String() string { - data, err := t.MarshalJSON() + data, err := json.Marshal(t) if err != nil { return err.Error() } return string(data) } -func (t Transaction) ValidData() error { - if err := t.Inputs.NoAddressIntersection(t.Outputs); err != nil { - return fmt.Errorf("Inputs and Outputs intersect: %w", err) - } - if err := t.Inputs.NFTokenIDsConserved(t.Outputs); err != nil { - return fmt.Errorf("Inputs and Outputs mismatch: %w", err) - } - // Coinbase transactions must only have one input. - if t.IsCoinbase() && len(t.Inputs) != 1 { - return fmt.Errorf("invalid coinbase transaction") - } - return nil -} - -// IsCoinbase returns true if the coinbase address is in t.Input. This does not -// necessarily mean that t is a valid coinbase transaction. -func (t Transaction) IsCoinbase() bool { - tkns := t.Inputs[fat.Coinbase()] - return len(tkns) != 0 -} - -// UnmarshalEntry unmarshals the entry content as a Transaction. -func (t *Transaction) UnmarshalEntry() error { - return t.Entry.UnmarshalEntry(t) -} - -// MarshalEntry marshals the Transaction into the Entry content. -func (t *Transaction) MarshalEntry() error { - return t.Entry.MarshalEntry(t) -} - -func (t *Transaction) Validate(idKey *factom.ID1Key) error { - if err := t.UnmarshalEntry(); err != nil { - return err - } - if err := t.ValidExtIDs(); err != nil { - return err - } - if t.IsCoinbase() { - if t.ID1Key() != *idKey { - return fmt.Errorf("invalid RCD") - } - } else { - if !t.ValidRCDs() { - return fmt.Errorf("invalid RCDs") - } - } - return nil -} - -func (t Transaction) ValidExtIDs() error { - return t.Entry.ValidExtIDs(len(t.Inputs)) -} - -func (t Transaction) ValidRCDs() bool { - // Create a map of all RCDs that are present in the ExtIDs. - adrs := make(map[factom.FAAddress]struct{}, len(t.Inputs)) - extIDs := t.ExtIDs[1:] - for i := 0; i < len(extIDs)/2; i++ { - adrs[t.FAAddress(i)] = struct{}{} - } - - // Ensure that for all Inputs there is a corresponding RCD in the - // ExtIDs. - for inputAdr := range t.Inputs { - if _, ok := adrs[inputAdr]; !ok { - return false - } +func (t Transaction) Sign(signingSet ...factom.RCDPrivateKey) (factom.Entry, error) { + e := t.Entry + content, err := json.Marshal(t) + if err != nil { + return e, err } - return true + e.Content = content + return fat103.Sign(e, signingSet...), nil } diff --git a/fat/fat1/transaction_test.go b/fat1/transaction_test.go similarity index 98% rename from fat/fat1/transaction_test.go rename to fat1/transaction_test.go index 58eedc6..ed8895f 100644 --- a/fat/fat1/transaction_test.go +++ b/fat1/transaction_test.go @@ -30,7 +30,7 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - . "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + . "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -252,7 +252,7 @@ var ( newNFTokens(NewNFTokenIDRange(6, 11))} identityChainID = factom.NewBytes32(validIdentityChainID()) - tokenChainID = fat.ChainID("test", identityChainID) + tokenChainID = fat.ComputeChainID("test", identityChainID) ) func newNFTokens(ids ...NFTokensSetter) NFTokens { @@ -320,7 +320,7 @@ func validCoinbaseTxEntryContentMap() map[string]interface{} { // inputs/outputs func inputs() map[string]NFTokens { - var inputs map[string]NFTokens + inputs := make(map[string]NFTokens) for i := range inputAddresses { tkns := newNFTokens() tkns.Append(inputNFTokens[i]) @@ -329,7 +329,7 @@ func inputs() map[string]NFTokens { return inputs } func outputs() map[string]NFTokens { - var outputs map[string]NFTokens + outputs := make(map[string]NFTokens) for i := range outputAddresses { tkns := newNFTokens() tkns.Append(outputNFTokens[i]) @@ -338,7 +338,7 @@ func outputs() map[string]NFTokens { return outputs } func coinbaseInputs() map[string]NFTokens { - var inputs map[string]NFTokens + inputs := make(map[string]NFTokens) for i := range coinbaseInputAddresses { tkns := newNFTokens() tkns.Append(coinbaseInputNFTokens[i]) @@ -347,7 +347,7 @@ func coinbaseInputs() map[string]NFTokens { return inputs } func coinbaseOutputs() map[string]NFTokens { - var outputs map[string]NFTokens + outputs := make(map[string]NFTokens) for i := range coinbaseOutputAddresses { tkns := newNFTokens() tkns.Append(coinbaseOutputNFTokens[i]) diff --git a/fat103/sign.go b/fat103/sign.go new file mode 100644 index 0000000..3f76796 --- /dev/null +++ b/fat103/sign.go @@ -0,0 +1,50 @@ +package fat103 + +import ( + "crypto/ed25519" + "crypto/sha512" + "math/rand" + "strconv" + "time" + + "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" +) + +// Sign the RCD/Sig ID Salt + Timestamp Salt + Chain ID Salt + Content of the +// factom.Entry and add the RCD + signature pairs for the given addresses to +// the ExtIDs. This clears any existing ExtIDs. +func Sign(e factom.Entry, signingSet ...factom.RCDPrivateKey) factom.Entry { + // Set the Entry's timestamp so that the signatures will verify against + // this time salt. + timeSalt := newTimestampSalt() + e.Timestamp = time.Now() + + // Compose the signed message data using exactly allocated bytes. + maxRcdSigIDSaltStrLen := jsonlen.Uint64(uint64(len(signingSet))) + maxMsgLen := maxRcdSigIDSaltStrLen + len(timeSalt) + len(e.ChainID) + len(e.Content) + msg := make(factom.Bytes, maxMsgLen) + i := maxRcdSigIDSaltStrLen + i += copy(msg[i:], timeSalt[:]) + i += copy(msg[i:], e.ChainID[:]) + copy(msg[i:], e.Content) + + // Generate the ExtIDs for each address in the signing set. + e.ExtIDs = make([]factom.Bytes, 1, len(signingSet)*2+1) + e.ExtIDs[0] = timeSalt + for rcdSigID, a := range signingSet { + // Compose the RcdSigID salt and prepend it to the message. + rcdSigIDSalt := strconv.FormatUint(uint64(rcdSigID), 10) + start := maxRcdSigIDSaltStrLen - len(rcdSigIDSalt) + copy(msg[start:], rcdSigIDSalt) + + msgHash := sha512.Sum512(msg[start:]) + sig := ed25519.Sign(a.PrivateKey(), msgHash[:]) + e.ExtIDs = append(e.ExtIDs, a.RCD(), sig) + } + return e +} +func newTimestampSalt() []byte { + timestamp := time.Now().Add(time.Duration(-rand.Int63n(int64(1 * time.Hour)))) + return []byte(strconv.FormatInt(timestamp.Unix(), 10)) +} diff --git a/fat103/validate.go b/fat103/validate.go new file mode 100644 index 0000000..6c0169c --- /dev/null +++ b/fat103/validate.go @@ -0,0 +1,114 @@ +// MIT License +// +// Copyright 2018 Canonical Ledgers, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +package fat103 + +import ( + "crypto/sha256" + "crypto/sha512" + "fmt" + "strconv" + "time" + + "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" + + "crypto/ed25519" +) + +// Validate validates the structure of the ExtIDs of the factom.Entry to make +// sure that it has a valid timestamp salt and a valid set of RCD/signature +// pairs. +func Validate(e factom.Entry, expected map[factom.Bytes32]struct{}) error { + if len(expected) == 0 || len(e.ExtIDs) != 2*len(expected)+1 { + return fmt.Errorf("invalid number of ExtIDs") + } + + // Validate Timestamp Salt + timestampSalt := string(e.ExtIDs[0]) + sec, err := strconv.ParseInt(timestampSalt, 10, 64) + if err != nil { + return fmt.Errorf("ExtIDs[0]: timestamp salt: %w", err) + } + ts := time.Unix(sec, 0) + diff := e.Timestamp.Sub(ts) + if -12*time.Hour > diff || diff > 12*time.Hour { + return fmt.Errorf("ExtIDs[0]: timestamp salt: expired") + } + + // Compose the signed message data using exactly allocated bytes. + numRcdSigPairs := len(e.ExtIDs) / 2 + maxRcdSigIDSalt := numRcdSigPairs - 1 + maxRcdSigIDSaltStrLen := jsonlen.Uint64(uint64(maxRcdSigIDSalt)) + timeSalt := e.ExtIDs[0] + maxMsgLen := maxRcdSigIDSaltStrLen + + len(timeSalt) + + len(e.ChainID) + + len(e.Content) + msg := make([]byte, maxMsgLen) + i := maxRcdSigIDSaltStrLen + i += copy(msg[i:], timeSalt) + i += copy(msg[i:], e.ChainID[:]) + copy(msg[i:], e.Content) + + rcdSigs := e.ExtIDs[1:] + for i := 0; i < len(rcdSigs); i += 2 { + rcd := rcdSigs[i] + if len(rcd) != factom.RCDSize { + return fmt.Errorf("ExtIDs[%v]: invalid RCD size", i+1) + } + if rcd[0] != factom.RCDType { + return fmt.Errorf("ExtIDs[%v]: invalid RCD type", i+1) + } + rcdHash := sha256d(rcd) + if _, ok := expected[rcdHash]; !ok { + return fmt.Errorf( + "ExtIDs[%v]: unexpected or duplicate RCD Hash", i) + } + delete(expected, rcdHash) + + sig := rcdSigs[i+1] + if len(sig) != factom.SignatureSize { + return fmt.Errorf("ExtIDs[%v]: invalid signature size", i+1) + } + + rcdSigID := i / 2 + // Prepend the RCD Sig ID Salt to the message data + rcdSigIDSalt := strconv.FormatUint(uint64(rcdSigID), 10) + start := maxRcdSigIDSaltStrLen - len(rcdSigIDSalt) + copy(msg[start:], rcdSigIDSalt) + + msgHash := sha512.Sum512(msg[start:]) + pubKey := []byte(rcd[1:]) // Omit RCD Type byte + if !ed25519.Verify(pubKey, msgHash[:], sig) { + return fmt.Errorf("ExtIDs[%v]: invalid signature", i+1+1) + } + } + + return nil +} + +// sha256d computes two rounds of the sha256 hash. +func sha256d(data []byte) factom.Bytes32 { + hash := sha256.Sum256(data) + return sha256.Sum256(hash[:]) +} diff --git a/fat103/validate_test.go b/fat103/validate_test.go new file mode 100644 index 0000000..6f22aff --- /dev/null +++ b/fat103/validate_test.go @@ -0,0 +1,218 @@ +package fat103 + +import ( + "math/rand" + "strconv" + "testing" + "time" + + "github.com/Factom-Asset-Tokens/factom" + "github.com/stretchr/testify/assert" +) + +func TestValidate(t *testing.T) { + for _, test := range validateTests { + test := test + t.Run(test.Name, func(t *testing.T) { testValidate(t, test) }) + } +} + +func testValidate(t *testing.T, test validateTest) { + assert := assert.New(t) + err := Validate(test.Entry, rcdHashes(test.Expected)) + if len(test.Error) == 0 { + assert.NoError(err) + return + } + assert.EqualError(err, test.Error) +} + +type validateTest struct { + Name string + factom.Entry + Expected []factom.RCDPrivateKey + Error string +} + +var validateTests = []validateTest{ + func() validateTest { + e, adrs := validEntry(2) + return validateTest{ + Name: "valid", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(100) + return validateTest{ + Name: "valid (large signing set)", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs = nil + return validateTest{ + Name: "nil ExtIDs", + Error: "invalid number of ExtIDs", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs = append(e.ExtIDs, factom.Bytes{}) + return validateTest{ + Name: "extra ExtIDs", + Error: "invalid number of ExtIDs", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs[0] = []byte("xxxx") + return validateTest{ + Name: "invalid timestamp (format)", + Error: "ExtIDs[0]: timestamp salt: strconv.ParseInt: parsing \"xxxx\": invalid syntax", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.Timestamp = time.Now().Add(-48 * time.Hour) + return validateTest{ + Name: "invalid timestamp (expired)", + Error: "ExtIDs[0]: timestamp salt: expired", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.Timestamp = time.Now().Add(48 * time.Hour) + return validateTest{ + Name: "invalid timestamp (expired)", + Error: "ExtIDs[0]: timestamp salt: expired", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs[1] = append(e.ExtIDs[1], 0x00) + return validateTest{ + Name: "invalid RCD size", + Error: "ExtIDs[1]: invalid RCD size", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs[1][0]++ + return validateTest{ + Name: "invalid RCD type", + Error: "ExtIDs[1]: invalid RCD type", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs[2] = append(e.ExtIDs[2], 0x00) + return validateTest{ + Name: "invalid signature size", + Error: "ExtIDs[1]: invalid signature size", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ExtIDs[2][0]++ + return validateTest{ + Name: "invalid signatures", + Error: "ExtIDs[2]: invalid signature", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + rcdSig := e.ExtIDs[1:3] + e.ExtIDs[1] = e.ExtIDs[3] + e.ExtIDs[2] = e.ExtIDs[4] + e.ExtIDs[3] = rcdSig[0] + e.ExtIDs[4] = rcdSig[1] + return validateTest{ + Name: "invalid signatures (transpose)", + Error: "ExtIDs[2]: invalid signature", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + ts := time.Now().Add(time.Duration( + -rand.Int63n(int64(12 * time.Hour)))) + timeSalt := []byte(strconv.FormatInt(ts.Unix(), 10)) + e.ExtIDs[0] = timeSalt + return validateTest{ + Name: "invalid signatures (timestamp)", + Error: "ExtIDs[2]: invalid signature", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, adrs := validEntry(2) + e.ChainID = new(factom.Bytes32) + e.ChainID[0] = 0x01 + e.ChainID[2] = 0x02 + return validateTest{ + Name: "invalid signatures (chain ID)", + Error: "ExtIDs[2]: invalid signature", + Entry: e, + Expected: adrs, + } + }(), func() validateTest { + e, _ := validEntry(2) + return validateTest{ + Name: "unexpected RCD", + Error: "ExtIDs[0]: unexpected or duplicate RCD Hash", + Entry: e, + Expected: genAddresses(2), + } + }(), func() validateTest { + e, adrs := validEntry(3) + e.ExtIDs = append(e.ExtIDs[:5], e.ExtIDs[1:3]...) + return validateTest{ + Name: "unexpected RCD (duplicate)", + Error: "ExtIDs[4]: unexpected or duplicate RCD Hash", + Entry: e, + Expected: adrs, + } + }(), +} + +func validEntry(n int) (factom.Entry, []factom.RCDPrivateKey) { + var e factom.Entry + e.Content = factom.Bytes("some data to sign") + e.ChainID = new(factom.Bytes32) + *e.ChainID = factom.ComputeChainID([]factom.Bytes{factom.Bytes("test chain ID")}) + // Generate valid signatures with blank Addresses. + adrs := genAddresses(n) + e = Sign(e, adrs...) + return e, adrs +} + +func genAddresses(n int) []factom.RCDPrivateKey { + adrs := make([]factom.RCDPrivateKey, n) + for i := range adrs { + adr, err := factom.GenerateFsAddress() + if err != nil { + panic(err) + } + adrs[i] = adr + } + return adrs +} + +func rcdHashes(adrs []factom.RCDPrivateKey) map[factom.Bytes32]struct{} { + rcdHashes := make(map[factom.Bytes32]struct{}, len(adrs)) + for _, adr := range adrs { + rcdHashes[sha256d(adr.RCD())] = struct{}{} + } + return rcdHashes +} diff --git a/go.mod b/go.mod index 0602a68..0d4a491 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d - github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d + github.com/Factom-Asset-Tokens/factom v0.0.0-20191021172540-f9e30125a179 github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index e7dc36f..2193dab 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d h1:DTgnFMb30d+3Q38c+UAfgZ/ePHhaDZFpg1jeAZbO74c= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191010221444-510331319e8d/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191021172540-f9e30125a179 h1:zpO4OYqH16iAngk4YIrC3FOJFyUWDgODp2NcdwlrUJM= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191021172540-f9e30125a179/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/internal/db/apply.go b/internal/db/apply.go index f3564c1..b61e98e 100644 --- a/internal/db/apply.go +++ b/internal/db/apply.go @@ -28,8 +28,8 @@ import ( "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" "github.com/Factom-Asset-Tokens/fatd/internal/db/eblocks" "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" @@ -76,7 +76,15 @@ func (chain *Chain) ApplyEntry(e factom.Entry) (txErr, err error) { var alwaysRollbackErr = fmt.Errorf("always rollback") func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error) { - issuance := fat.NewIssuance(e) + // The Identity must exist prior to issuance. + if !chain.Identity.IsPopulated() || e.Timestamp.Before(chain.Identity.Timestamp) { + issueErr = fmt.Errorf("Identity not set up prior to this Entry") + return + } + issuance, issueErr := fat.NewIssuance(e, (*factom.Bytes32)(chain.Identity.ID1Key)) + if issueErr != nil { + return + } rollback := sqlitex.Save(chain.Conn) chainCopy := *chain defer func() { @@ -94,13 +102,6 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error rollback(&err) // commit //chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) }() - // The Identity must exist prior to issuance. - if !chain.Identity.IsPopulated() || e.Timestamp.Before(chain.Identity.Timestamp) { - return - } - if issueErr = issuance.Validate(chain.ID1); issueErr != nil { - return - } if err = metadata.SetInitEntryID(chain.Conn, ei); err != nil { return } @@ -110,7 +111,7 @@ func (chain *Chain) applyIssuance(ei int64, e factom.Entry) (issueErr, err error } func (chain *Chain) setApplyFunc() { - if !chain.Issuance.IsPopulated() { + if !chain.Issuance.Entry.IsPopulated() { chain.apply = func(chain *Chain, ei int64, e factom.Entry) ( txErr, err error) { txErr, err = chain.applyIssuance(ei, e) @@ -119,7 +120,7 @@ func (chain *Chain) setApplyFunc() { return } // Adapt to match ApplyFunc. - switch chain.Type { + switch chain.Issuance.Type { case fat0.Type: chain.apply = func(chain *Chain, ei int64, e factom.Entry) ( txErr, err error) { @@ -137,7 +138,7 @@ func (chain *Chain) setApplyFunc() { } } -func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { +func (chain *Chain) Save() func(txErr, err *error) { rollback := sqlitex.Save(chain.Conn) chainCopy := *chain return func(txErr, err *error) { @@ -149,25 +150,15 @@ func (chain *Chain) Save(tx fat.Transaction) func(txErr, err *error) { if *err != nil { return } - //chain.Log.Debugf("Entry{%v}: invalid %v Transaction: %v", - // e.Hash, chain.Type, *txErr) return } rollback(err) - //var cbStr string - //if tx.IsCoinbase() { - // cbStr = "Coinbase " - //} - //chain.Log.Debugf("Valid %v %vTransaction: %v %+v", - // chain.Type, cbStr, e.Hash, tx) } } -func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { - if txErr = tx.Validate(chain.ID1); txErr != nil { - return - } - e := tx.FactomEntry() +func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx fat0.Transaction, + txErr, err error) { + valid, err := entries.CheckUniquelyValid(chain.Conn, ei, e.Hash) if err != nil { return @@ -177,24 +168,21 @@ func (chain *Chain) applyTx(ei int64, tx fat.Transaction) (txErr, err error) { return } - if err = entries.SetValid(chain.Conn, ei); err != nil { + tx, txErr = fat0.NewTransaction(e, (*factom.Bytes32)(chain.Identity.ID1Key)) + if txErr != nil { return } - return -} -func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, - txErr, err error) { - tx = fat0.NewTransaction(e) - defer chain.Save(tx)(&txErr, &err) + defer chain.Save()(&txErr, &err) - if txErr, err = chain.applyTx(ei, tx); err != nil || txErr != nil { + if err = entries.SetValid(chain.Conn, ei); err != nil { return } if tx.IsCoinbase() { addIssued := tx.Inputs[fat.Coinbase()] - if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + if chain.Issuance.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Issuance.Supply { txErr = fmt.Errorf("coinbase exceeds max supply") return } @@ -234,19 +222,34 @@ func (chain *Chain) ApplyFAT0Tx(ei int64, e factom.Entry) (tx *fat0.Transaction, return } -func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx *fat1.Transaction, +func (chain *Chain) ApplyFAT1Tx(ei int64, e factom.Entry) (tx fat1.Transaction, txErr, err error) { - tx = fat1.NewTransaction(e) - defer chain.Save(tx)(&txErr, &err) - if txErr, err = chain.applyTx(ei, tx); err != nil || txErr != nil { + valid, err := entries.CheckUniquelyValid(chain.Conn, ei, e.Hash) + if err != nil { + return + } + if !valid { + txErr = fmt.Errorf("replay: hash previously marked valid") + return + } + + tx, txErr = fat1.NewTransaction(e, (*factom.Bytes32)(chain.Identity.ID1Key)) + if txErr != nil { + return + } + + defer chain.Save()(&txErr, &err) + + if err = entries.SetValid(chain.Conn, ei); err != nil { return } if tx.IsCoinbase() { nfTkns := tx.Inputs[fat.Coinbase()] addIssued := uint64(len(nfTkns)) - if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + if chain.Issuance.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Issuance.Supply { txErr = fmt.Errorf("coinbase exceeds max supply") return } diff --git a/internal/db/chain.go b/internal/db/chain.go index d6b045a..b177648 100644 --- a/internal/db/chain.go +++ b/internal/db/chain.go @@ -66,14 +66,14 @@ type Chain struct { // FAT Specific Data TokenID string IssuerChainID *factom.Bytes32 - factom.Identity - fat.Issuance - NumIssued uint64 + Identity factom.Identity + Issuance fat.Issuance + NumIssued uint64 - DBFile string - *sqlite.Conn // Read/Write - *sqlitex.Pool // Read Only Pool - Log _log.Log + DBFile string + Conn *sqlite.Conn // Read/Write + Pool *sqlitex.Pool // Read Only Pool + Log _log.Log apply applyFunc } @@ -87,7 +87,7 @@ func OpenNew(ctx context.Context, dbPath string, path := dbPath + fname nameIDs := eb.Entries[0].ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { + if !fat.ValidNameIDs(nameIDs) { err = fmt.Errorf("invalid token chain Name IDs") return } @@ -118,7 +118,7 @@ func OpenNew(ctx context.Context, dbPath string, chain.DBFile = fname chain.ID = eb.ChainID chain.IssuerChainID = new(factom.Bytes32) - chain.TokenID, *chain.IssuerChainID = fat.TokenIssuer(nameIDs) + chain.TokenID, *chain.IssuerChainID = fat.ParseTokenIssuer(nameIDs) chain.HeadDBKeyMR = dbKeyMR chain.Identity = identity chain.SyncHeight = eb.Height @@ -206,11 +206,11 @@ func fnameToChainID(fname string) (*factom.Bytes32, error) { dbFileExtension { return nil, invalidFName } - chainID := factom.NewBytes32FromString(fname[0:64]) - if chainID == nil { + chainID := factom.NewBytes32(fname[0:64]) + if chainID.IsZero() { return nil, invalidFName } - return chainID, nil + return &chainID, nil } func OpenConnPool(ctx context.Context, dbURI string) ( @@ -341,11 +341,11 @@ func (chain *Chain) loadMetadata() error { } nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { + if !fat.ValidNameIDs(nameIDs) { return fmt.Errorf("invalid token chain Name IDs") } chain.IssuerChainID = new(factom.Bytes32) - chain.TokenID, *chain.IssuerChainID = fat.TokenIssuer(nameIDs) + chain.TokenID, *chain.IssuerChainID = fat.ParseTokenIssuer(nameIDs) // Load Chain Head eb, dbKeyMR, err := eblocks.SelectLatest(chain.Conn) @@ -363,5 +363,9 @@ func (chain *Chain) loadMetadata() error { chain.SyncHeight, chain.NumIssued, chain.SyncDBKeyMR, chain.NetworkID, chain.Identity, chain.Issuance, err = metadata.Select(chain.Conn) + if err != nil { + return err + } + return err } diff --git a/internal/db/entries/entries.go b/internal/db/entries/entries.go index 05cf5a5..16dcca9 100644 --- a/internal/db/entries/entries.go +++ b/internal/db/entries/entries.go @@ -32,7 +32,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" ) diff --git a/internal/db/metadata/metadata.go b/internal/db/metadata/metadata.go index 70038d2..02d2290 100644 --- a/internal/db/metadata/metadata.go +++ b/internal/db/metadata/metadata.go @@ -168,11 +168,13 @@ func Select(conn *sqlite.Conn) (syncHeight uint32, numIssued uint64, return } initEntryID := stmt.ColumnInt64(5) - issuance.Entry.Entry, err = entries.SelectByID(conn, initEntryID) + e, err := entries.SelectByID(conn, initEntryID) if err != nil { return } - if err = issuance.Validate(identity.ID1); err != nil { + + issuance, err = fat.NewIssuance(e, (*factom.Bytes32)(identity.ID1Key)) + if err != nil { return } diff --git a/internal/db/nftokens/nftokens.go b/internal/db/nftokens/nftokens.go index ac828c6..f1bf425 100644 --- a/internal/db/nftokens/nftokens.go +++ b/internal/db/nftokens/nftokens.go @@ -32,7 +32,7 @@ import ( "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" "github.com/Factom-Asset-Tokens/factom" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/Factom-Asset-Tokens/fatd/internal/db/sqlbuilder" ) diff --git a/internal/db/nftokens/txrelation.go b/internal/db/nftokens/txrelation.go index 17850e9..d1a45b5 100644 --- a/internal/db/nftokens/txrelation.go +++ b/internal/db/nftokens/txrelation.go @@ -2,7 +2,7 @@ package nftokens import ( "crawshaw.io/sqlite" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat1" ) // CreateTableTransactions is a SQL string that creates the diff --git a/internal/engine/chain.go b/internal/engine/chain.go index f2883e7..b6eadf5 100644 --- a/internal/engine/chain.go +++ b/internal/engine/chain.go @@ -64,7 +64,7 @@ func OpenNew(ctx context.Context, c *factom.Client, dbKeyMR *factom.Bytes32, eb factom.EBlock) (chain Chain, err error) { var identity factom.Identity identity.ChainID = new(factom.Bytes32) - _, *identity.ChainID = fat.TokenIssuer(eb.Entries[0].ExtIDs) + _, *identity.ChainID = fat.ParseTokenIssuer(eb.Entries[0].ExtIDs) if err = identity.Get(ctx, c); err != nil { // A jsonrpc2.Error indicates that the identity chain // doesn't yet exist, which we tolerate. @@ -82,7 +82,7 @@ func OpenNew(ctx context.Context, c *factom.Client, if err != nil { return chain, fmt.Errorf("db.OpenNew(): %w", err) } - if chain.Issuance.IsPopulated() { + if chain.Issuance.Entry.IsPopulated() { chain.ChainStatus = ChainStatusIssued } else { chain.ChainStatus = ChainStatusTracked @@ -123,7 +123,7 @@ func OpenNewByChainID(ctx context.Context, } nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { + if !fat.ValidNameIDs(nameIDs) { err = fmt.Errorf("not a valid FAT chain: %v", chainID) return } @@ -192,7 +192,7 @@ func (chain *Chain) Apply(ctx context.Context, c *factom.Client, return err } // Update ChainStatus - if !chain.IsIssued() && chain.Issuance.IsPopulated() { + if !chain.IsIssued() && chain.Issuance.Entry.IsPopulated() { chain.ChainStatus = ChainStatusIssued } return nil diff --git a/internal/engine/chainmap.go b/internal/engine/chainmap.go index 899dd24..2005a91 100644 --- a/internal/engine/chainmap.go +++ b/internal/engine/chainmap.go @@ -184,7 +184,7 @@ func loadChains(ctx context.Context) (syncHeight uint32, err error) { chain.ChainStatus = ChainStatusTracked Chains.trackedIDs = append(Chains.trackedIDs, chain.ID) - if chain.Issuance.IsPopulated() { + if chain.Issuance.Entry.IsPopulated() { chain.ChainStatus = ChainStatusIssued Chains.issuedIDs = append(Chains.issuedIDs, chain.ID) } diff --git a/internal/engine/engine.go b/internal/engine/engine.go index fbaba11..c7dcee8 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -331,25 +331,25 @@ func engine(ctx context.Context, done chan struct{}) { } log.Errorf("factom.PendingEntries.Get(): %v", err) } - for i, j := 0, 0; i < len(pe); i = j { e := pe[i] - // Unrevealed entries have no ChainID - // and are at the end of the slice. + + // Unrevealed entries have no ChainID and are at the + // end of the slice. if e.ChainID == nil { // No more revealed entries. break } - // Grab any subsequent entries with - // this ChainID. + + // Grab any subsequent entries with this ChainID. for j = i + 1; j < len(pe); j++ { chainID := pe[j].ChainID if chainID == nil || *chainID != *e.ChainID { break } } - // Process all pending entries for this - // chain. + + // Process all pending entries for this chain. if err := ProcessPending(ctx, pe[i:j]...); err != nil { if ctx.Err() == nil { log.Errorf("ChainID(%v): %v", @@ -358,6 +358,7 @@ func engine(ctx context.Context, done chan struct{}) { return } } + wait: // Wait until the next scan tick or we're told to stop. select { diff --git a/internal/engine/process.go b/internal/engine/process.go index effd9d8..f239a80 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -62,7 +62,7 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err } // Ignore chains with NameIDs that don't match the fat pattern. nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { + if !fat.ValidNameIDs(nameIDs) { Chains.ignore(eb.ChainID) return nil } diff --git a/fat/jsonlen/compact.go b/internal/jsonlen/compact.go similarity index 100% rename from fat/jsonlen/compact.go rename to internal/jsonlen/compact.go diff --git a/fat/jsonlen/compact_test.go b/internal/jsonlen/compact_test.go similarity index 100% rename from fat/jsonlen/compact_test.go rename to internal/jsonlen/compact_test.go diff --git a/fat/jsonlen/number.go b/internal/jsonlen/number.go similarity index 100% rename from fat/jsonlen/number.go rename to internal/jsonlen/number.go diff --git a/fat/jsonlen/number_test.go b/internal/jsonlen/number_test.go similarity index 100% rename from fat/jsonlen/number_test.go rename to internal/jsonlen/number_test.go diff --git a/internal/srv/methods.go b/internal/srv/methods.go index d23fc14..536756d 100644 --- a/internal/srv/methods.go +++ b/internal/srv/methods.go @@ -34,8 +34,8 @@ import ( "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/api" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" + "github.com/Factom-Asset-Tokens/fatd/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat1" "github.com/Factom-Asset-Tokens/fatd/internal/db/addresses" "github.com/Factom-Asset-Tokens/fatd/internal/db/entries" "github.com/Factom-Asset-Tokens/fatd/internal/db/nftokens" @@ -76,7 +76,7 @@ func getIssuance(entry bool) jsonrpc2.MethodFunc { defer put() if entry { - return chain.Issuance.Entry.Entry + return chain.Issuance.Entry } return api.ResultGetIssuance{ ParamsToken: api.ParamsToken{ @@ -84,8 +84,8 @@ func getIssuance(entry bool) jsonrpc2.MethodFunc { TokenID: chain.TokenID, IssuerChainID: chain.Identity.ChainID, }, - Hash: chain.Issuance.Hash, - Timestamp: chain.Issuance.Timestamp.Unix(), + Hash: chain.Issuance.Entry.Hash, + Timestamp: chain.Issuance.Entry.Timestamp.Unix(), Issuance: chain.Issuance, } } @@ -118,16 +118,18 @@ func getTransaction(getEntry bool) jsonrpc2.MethodFunc { Pending: chain.LatestEntryTimestamp().Before(entry.Timestamp), } - var tx fat.Transaction - switch chain.Type { + var tx interface{} + switch chain.Issuance.Type { case fat0.Type: - tx = fat0.NewTransaction(entry) + tx, err = fat0.NewTransaction(entry, + (*factom.Bytes32)(chain.Identity.ID1Key)) case fat1.Type: - tx = fat1.NewTransaction(entry) + tx, err = fat1.NewTransaction(entry, + (*factom.Bytes32)(chain.Identity.ID1Key)) default: - panic(fmt.Sprintf("unknown FAT type: %v", chain.Type)) + panic(fmt.Sprintf("unknown FAT type: %v", chain.Issuance.Type)) } - if err := tx.UnmarshalEntry(); err != nil { + if err != nil { panic(err) } result.Tx = tx @@ -144,7 +146,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { } defer put() - if params.NFTokenID != nil && chain.Type != fat1.Type { + if params.NFTokenID != nil && chain.Issuance.Type != fat1.Type { err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err @@ -177,21 +179,25 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { txs := make([]api.ResultGetTransaction, len(entries)) for i := range txs { entry := entries[i] - var tx fat.Transaction - switch chain.Type { + var tx interface{} + switch chain.Issuance.Type { case fat0.Type: - tx = fat0.NewTransaction(entry) + tx, err = fat0.NewTransaction(entry, + (*factom.Bytes32)(chain.Identity.ID1Key)) case fat1.Type: - tx = fat1.NewTransaction(entry) + tx, err = fat1.NewTransaction(entry, + (*factom.Bytes32)(chain.Identity.ID1Key)) default: - panic(fmt.Sprintf("unknown FAT type: %v", chain.Type)) + panic(fmt.Sprintf("unknown FAT type: %v", + chain.Issuance.Type)) } - if err := tx.UnmarshalEntry(); err != nil { + if err != nil { panic(err) } txs[i].Hash = entry.Hash txs[i].Timestamp = entry.Timestamp.Unix() - txs[i].Pending = chain.LatestEntryTimestamp().Before(entry.Timestamp) + txs[i].Pending = chain.LatestEntryTimestamp(). + Before(entry.Timestamp) txs[i].Tx = tx } return txs @@ -248,7 +254,7 @@ func getNFBalance(ctx context.Context, data json.RawMessage) interface{} { } defer put() - if chain.Type != fat1.Type { + if chain.Issuance.Type != fat1.Type { err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err @@ -298,7 +304,7 @@ func getStats(ctx context.Context, data json.RawMessage) interface{} { CirculatingSupply: chain.NumIssued - burned, Burned: burned, Transactions: txCount, - IssuanceTimestamp: chain.Issuance.Timestamp.Unix(), + IssuanceTimestamp: chain.Issuance.Entry.Timestamp.Unix(), LastTransactionTimestamp: e.Timestamp.Unix(), NonZeroBalances: nonZeroBalances, } @@ -308,7 +314,7 @@ func getStats(ctx context.Context, data json.RawMessage) interface{} { res.ChainID = chain.ID res.TokenID = chain.TokenID res.IssuerChainID = chain.Identity.ChainID - res.IssuanceHash = chain.Issuance.Hash + res.IssuanceHash = chain.Issuance.Entry.Hash return res } @@ -320,7 +326,7 @@ func getNFToken(ctx context.Context, data json.RawMessage) interface{} { } defer put() - if chain.Type != fat1.Type { + if chain.Issuance.Type != fat1.Type { err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err @@ -359,7 +365,7 @@ func getNFTokens(ctx context.Context, data json.RawMessage) interface{} { } defer put() - if chain.Type != fat1.Type { + if chain.Issuance.Type != fat1.Type { err := api.ErrorTokenNotFound err.Data = "Token Chain is not FAT-1" return err @@ -399,7 +405,7 @@ func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { entry := params.Entry() var txErr error - switch chain.Type { + switch chain.Issuance.Type { case fat0.Type: txErr, err = attemptApplyFAT0Tx(chain, entry) case fat1.Type: @@ -445,12 +451,24 @@ func sendTransaction(ctx context.Context, data json.RawMessage) interface{} { } func attemptApplyFAT0Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) { // Validate tx - tx := fat0.NewTransaction(e) - txErr, err = applyTx(chain, tx) + valid, err := entries.CheckUniquelyValid(chain.Conn, 0, e.Hash) + if err != nil { + return + } + if !valid { + txErr = fmt.Errorf("replay: hash previously marked valid") + return + } + + tx, txErr := fat0.NewTransaction(e, (*factom.Bytes32)(chain.Identity.ID1Key)) + if txErr != nil { + return + } if tx.IsCoinbase() { addIssued := tx.Inputs[fat.Coinbase()] - if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + if chain.Issuance.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Issuance.Supply { txErr = fmt.Errorf("coinbase exceeds max supply") return } @@ -472,13 +490,25 @@ func attemptApplyFAT0Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) } func attemptApplyFAT1Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) { // Validate tx - tx := fat1.NewTransaction(e) - txErr, err = applyTx(chain, tx) + valid, err := entries.CheckUniquelyValid(chain.Conn, 0, e.Hash) + if err != nil { + return + } + if !valid { + txErr = fmt.Errorf("replay: hash previously marked valid") + return + } + + tx, txErr := fat1.NewTransaction(e, (*factom.Bytes32)(chain.Identity.ID1Key)) + if txErr != nil { + return + } if tx.IsCoinbase() { nfTkns := tx.Inputs[fat.Coinbase()] addIssued := uint64(len(nfTkns)) - if chain.Supply > 0 && int64(chain.NumIssued+addIssued) > chain.Supply { + if chain.Issuance.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Issuance.Supply { txErr = fmt.Errorf("coinbase exceeds max supply") return } @@ -527,22 +557,6 @@ func attemptApplyFAT1Tx(chain *engine.Chain, e factom.Entry) (txErr, err error) } return } -func applyTx(chain *engine.Chain, tx fat.Transaction) (txErr, err error) { - if txErr = tx.Validate(chain.ID1); txErr != nil { - return - } - e := tx.FactomEntry() - valid, err := entries.CheckUniquelyValid(chain.Conn, 0, e.Hash) - if err != nil { - return - } - if !valid { - txErr = fmt.Errorf("replay: hash previously marked valid") - return - } - return -} - func getDaemonTokens(ctx context.Context, data json.RawMessage) interface{} { if _, _, err := validate(ctx, data, nil); err != nil { return err From f282a451994d994220e644b2ea49a0bdfaeeedb7 Mon Sep 17 00:00:00 2001 From: Paul Bernier Date: Mon, 21 Oct 2019 16:22:08 -0700 Subject: [PATCH 103/124] fix(flag) add localnet to networkid help description --- internal/flag/flag.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 8ec6a03..be89bc9 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -145,7 +145,7 @@ var ( "factomdpassword": "Password for API connections to factomd", //"factomdcert": "The TLS certificate that will be provided by the factomd API server", //"factomdtls": "Set to true to use TLS when accessing the factomd API", - "networkid": `Accepts "main", "test", or four bytes in hex`, + "networkid": `Accepts "main", "test", "localnet", or four bytes in hex`, "w": "IPAddr:port# of factom-walletd API to use to access wallet", "wallettimeout": "Timeout for factom-walletd API requests, 0 means never timeout", From 2edd63fc1974668bf25fec8a8bf8bcaa96308d90 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 21 Oct 2019 16:46:59 -0800 Subject: [PATCH 104/124] refactor(flag): Change default db path to ~/.fatd --- internal/flag/flag.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/flag/flag.go b/internal/flag/flag.go index be89bc9..728b421 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -89,13 +89,10 @@ var ( "disablepending": false, "dbpath": func() string { - if cache, err := os.UserCacheDir(); err == nil { - return cache + "/fatd" + if home, err := os.UserHomeDir(); err != nil { + return "./fatd.db" } - if home, err := os.UserHomeDir(); err == nil { - return home + "/.fatd" - } - return "./fatd.db" + return home + "/.fatd" }(), "apiaddress": ":8078", From 8e7707cedfb7fe0e532fda802c2e9803b0496d8a Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 21 Oct 2019 16:55:49 -0800 Subject: [PATCH 105/124] fix(flag): Correct compile issue --- internal/flag/flag.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 728b421..868c0fd 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -89,10 +89,10 @@ var ( "disablepending": false, "dbpath": func() string { - if home, err := os.UserHomeDir(); err != nil { - return "./fatd.db" + if home, err := os.UserHomeDir(); err == nil { + return home + "/.fatd" } - return home + "/.fatd" + return "./fatd.db" }(), "apiaddress": ":8078", From f0aaba51fdb33487a446bbe3fe7ee199b7a09bfe Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Tue, 22 Oct 2019 13:30:33 -0800 Subject: [PATCH 106/124] refactor(fat1): Reduce exported API, move validation into NewTransaction, out of UnmarshalJSON --- cli/transact.go | 24 ++-- cli/transactfat1.go | 68 +---------- fat0/addressamountmap.go | 2 +- fat1/addressnftokensmap.go | 96 ++++------------ fat1/nftokenid.go | 10 +- fat1/nftokenidrange.go | 81 ++++++++----- fat1/nftokenmetadata.go | 10 +- fat1/nftokens.go | 190 ++++++++++++++----------------- fat1/transaction.go | 15 +-- internal/db/nftokens/nftokens.go | 6 +- internal/srv/methods.go | 2 +- 11 files changed, 199 insertions(+), 305 deletions(-) diff --git a/cli/transact.go b/cli/transact.go index 65259be..dcd0528 100644 --- a/cli/transact.go +++ b/cli/transact.go @@ -218,7 +218,11 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { fat0Tx.Inputs[fat.Coinbase()] = fat0Tx.Outputs.Sum() case fat1.Type: fat1Tx.Inputs = make(fat1.AddressNFTokensMap, 1) - fat1Tx.Inputs[fat.Coinbase()] = fat1Tx.Outputs.AllNFTokens() + tkns, err := fat1Tx.Outputs.AllNFTokens() + if err != nil { + errLog.Fatal(err) + } + fat1Tx.Inputs[fat.Coinbase()] = tkns } } @@ -296,12 +300,10 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { "get-nf-balance", params, &balance); err != nil { errLog.Fatal(err) } - if err := balance.ContainsAll(fat1Tx.Inputs[adr]); err != nil { - tknID := fat1.NFTokenID( - err.(fat1.ErrorMissingNFTokenID)) - errLog.Fatalf( - "--input %v:%v does not own NFTokenID %v", - adr, addressValueStrMap[adr], tknID) + if err := balance.ContainsAll( + fat1Tx.Inputs[adr]); err != nil { + errLog.Fatalf("--input %v:%v balance: %v", + adr, addressValueStrMap[adr], err) } } } @@ -316,7 +318,7 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { case fat0.Type: issuing = fat0Tx.Inputs.Sum() case fat1.Type: - issuing = uint64(len(fat1Tx.Inputs.AllNFTokens())) + issuing = fat1Tx.Inputs.Sum() } issued := stats.CirculatingSupply + stats.Burned if stats.Issuance.Supply != -1 && @@ -326,7 +328,11 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { } if cmdType == fat1.Type { params := api.ParamsGetNFToken{ParamsToken: params} - for tknID := range fat1Tx.Inputs.AllNFTokens() { + tkns, err := fat1Tx.Inputs.AllNFTokens() + if err != nil { + errLog.Fatal(err) + } + for tknID := range tkns { params.NFTokenID = &tknID err := FATClient.Request(context.Background(), "get-nf-token", params, nil) diff --git a/cli/transactfat1.go b/cli/transactfat1.go index 3a82e2d..59391a2 100644 --- a/cli/transactfat1.go +++ b/cli/transactfat1.go @@ -147,7 +147,7 @@ func (m AddressNFTokensMap) set(data string) error { return err } - m[fa] = fat1.NFTokens(tkns) + m[fa] = tkns.NFTokens addressValueStrMap[fa] = tknIDsStr return nil } @@ -158,69 +158,11 @@ func (AddressNFTokensMap) Type() string { return ":[,-]" } -type NFTokens fat1.NFTokens +type NFTokens struct{ fat1.NFTokens } func (tkns *NFTokens) Set(adrAmtStr string) error { - if *tkns == nil { - *tkns = make(NFTokens) + if tkns.NFTokens == nil { + tkns.NFTokens = make(fat1.NFTokens) } - return tkns.set(adrAmtStr) -} -func (tkns NFTokens) set(data string) error { - if len(data) < 2 || data[0] != '[' || data[len(data)-1] != ']' { - return fmt.Errorf("invalid NFTokenIDs format") - } - data = data[1 : len(data)-1] // Trim '[' and ']' - - // Split NFTokenIDs or NFTokenIDRanges on ',' - tknIDStrs := strings.Split(data, ",") - for _, tknIDStr := range tknIDStrs { - var tknIDs fat1.NFTokensSetter - tknRangeStrs := strings.Split(tknIDStr, "-") - switch len(tknRangeStrs) { - case 1: - // Parse single NFToken - tknID, err := parseNFTokenID(tknIDStr) - if err != nil { - return err - } - tknIDs = tknID - case 2: - minMax := make([]fat1.NFTokenID, 2) - for i, tknIDStr := range tknRangeStrs { - if len(tknIDStr) == 0 { - return fmt.Errorf("invalid NFTokenIDRange format: %v", - tknIDStr) - } - tknID, err := parseNFTokenID(tknIDStr) - if err != nil { - return err - } - minMax[i] = tknID - } - if minMax[0] > minMax[1] { - return fmt.Errorf("invalid NFTokenIDRange: %v > %v", - minMax[0], minMax[1]) - } - tknIDs = fat1.NewNFTokenIDRange(minMax...) - default: - return fmt.Errorf("invalid NFTokenIDRange format: %v", tknIDStr) - } - // Set all NFTokenIDs to the NFTokens map. - if err := fat1.NFTokens(tkns).Set(tknIDs); err != nil { - return fmt.Errorf("invalid NFTokens: %w", err) - } - } - return nil -} -func (tkns NFTokens) String() string { - return fmt.Sprintf("%v", fat1.NFTokens(tkns)) -} - -func parseNFTokenID(tknIDStr string) (fat1.NFTokenID, error) { - tknID, err := parsePositiveInt(tknIDStr) - if err != nil { - return 0, fmt.Errorf("invalid NFTokenID: %w", err) - } - return fat1.NFTokenID(tknID), nil + return tkns.UnmarshalText([]byte(adrAmtStr)) } diff --git a/fat0/addressamountmap.go b/fat0/addressamountmap.go index b85dde9..b76c983 100644 --- a/fat0/addressamountmap.go +++ b/fat0/addressamountmap.go @@ -86,7 +86,7 @@ func (m AddressAmountMap) Sum() uint64 { return sum } -func (m AddressAmountMap) NoAddressIntersection(n AddressAmountMap) error { +func (m AddressAmountMap) noAddressIntersection(n AddressAmountMap) error { short, long := m, n if len(short) > len(long) { short, long = long, short diff --git a/fat1/addressnftokensmap.go b/fat1/addressnftokensmap.go index f24d5bc..9ef16c9 100644 --- a/fat1/addressnftokensmap.go +++ b/fat1/addressnftokensmap.go @@ -34,18 +34,8 @@ import ( type AddressNFTokensMap map[factom.FAAddress]NFTokens func (m AddressNFTokensMap) MarshalJSON() ([]byte, error) { - if m.NumNFTokenIDs() == 0 { - return nil, fmt.Errorf("empty") - } - if err := m.NoInternalNFTokensIntersection(); err != nil { - return nil, err - } adrStrTknsMap := make(map[string]NFTokens, len(m)) for adr, tkns := range m { - // Omit addresses with empty NFTokens. - if len(tkns) == 0 { - continue - } adrStrTknsMap[adr.String()] = tkns } return json.Marshal(adrStrTknsMap) @@ -64,13 +54,13 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { adrJSONLen := len(`"":,`) + adrStrLen expectedJSONLen := len(`{}`) - len(`,`) + len(adrStrDataMap)*adrJSONLen *m = make(AddressNFTokensMap, len(adrStrDataMap)) - var adr factom.FAAddress - var tkns NFTokens var numTkns int for adrStr, data := range adrStrDataMap { - if err := adr.Set(adrStr); err != nil { + adr, err := factom.NewFAAddress(adrStr) + if err != nil { return fmt.Errorf("%T: %#v: %w", m, adrStr, err) } + var tkns NFTokens if err := tkns.UnmarshalJSON(data); err != nil { return fmt.Errorf("%T: %v: %w", m, err, adr) } @@ -79,9 +69,6 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { return fmt.Errorf("%T(len:%v): %T(len:%v): %v", m, numTkns-len(tkns), tkns, len(tkns), ErrorCapacity) } - if err := m.NoNFTokensIntersection(tkns); err != nil { - return fmt.Errorf("%T: %w and %v", m, err, adr) - } (*m)[adr] = tkns expectedJSONLen += len(jsonlen.Compact(data)) } @@ -91,81 +78,40 @@ func (m *AddressNFTokensMap) UnmarshalJSON(data []byte) error { return nil } -func (m AddressNFTokensMap) NoNFTokensIntersection(newTkns NFTokens) error { - for adr, existingTkns := range m { - if err := existingTkns.NoIntersection(newTkns); err != nil { - return fmt.Errorf("%w: %v", err, adr) - } - } - return nil -} - -func (m AddressNFTokensMap) NoAddressIntersection(n AddressNFTokensMap) error { - short, long := m, n - if len(short) > len(long) { - short, long = long, short - } - for rcdHash, tkns := range short { - if len(tkns) == 0 { - continue - } - if tkns := long[rcdHash]; len(tkns) != 0 { - return fmt.Errorf("duplicate address: %v", rcdHash) - } - } - return nil -} - -func (m AddressNFTokensMap) NFTokenIDsConserved(n AddressNFTokensMap) error { - numTknIDs := m.NumNFTokenIDs() - if numTknIDs != n.NumNFTokenIDs() { +func (m AddressNFTokensMap) nfTokenIDsConserved(n AddressNFTokensMap) error { + if m.Sum() != n.Sum() { return fmt.Errorf("number of NFTokenIDs differ") } - allTkns := m.AllNFTokens() + allTkns, err := m.AllNFTokens() + if err != nil { + return err + } for _, tkns := range n { - for tknID := range tkns { - if _, ok := allTkns[tknID]; !ok { - return fmt.Errorf("missing NFTokenID: %v", tknID) - } + if err := allTkns.ContainsAll(tkns); err != nil { + return err } } return nil } -func (m AddressNFTokensMap) AllNFTokens() NFTokens { - allTkns := make(NFTokens, len(m)) +func (m AddressNFTokensMap) AllNFTokens() (NFTokens, error) { + allTkns := make(NFTokens, m.Sum()) for _, tkns := range m { for tknID := range tkns { - allTkns[tknID] = struct{}{} + if err := allTkns.set(tknID); err != nil { + return nil, err + } } } - return allTkns + return allTkns, nil } -func (m AddressNFTokensMap) NumNFTokenIDs() int { - var numTknIDs int +func (m AddressNFTokensMap) Sum() uint64 { + var sum uint64 for _, tkns := range m { - numTknIDs += len(tkns) + sum += uint64(len(tkns)) } - return numTknIDs -} - -func (m AddressNFTokensMap) NoInternalNFTokensIntersection() error { - allTkns := make(NFTokens, m.NumNFTokenIDs()) - for rcdHash, tkns := range m { - if err := allTkns.Append(tkns); err != nil { - // We found an intersection. To identify the other - // RCDHash that owns tknID, we temporarily remove - // rcdHash from m and restore it after we return. - tknID := NFTokenID(err.(ErrorNFTokenIDIntersection)) - delete(m, rcdHash) - otherRCDHash := m.Owner(tknID) - m[rcdHash] = tkns - return fmt.Errorf("%w: %v and %v", err, rcdHash, otherRCDHash) - - } - } - return nil + return sum } func (m AddressNFTokensMap) Owner(tknID NFTokenID) factom.FAAddress { diff --git a/fat1/nftokenid.go b/fat1/nftokenid.go index 73e115e..1da8c1a 100644 --- a/fat1/nftokenid.go +++ b/fat1/nftokenid.go @@ -31,9 +31,8 @@ import ( // NFTokenID is a Non-Fungible Token ID. type NFTokenID uint64 -// Set id in nfTkns and return an error if it is already set. -func (id NFTokenID) Set(tkns NFTokens) error { - if len(tkns)+id.Len() > maxCapacity { +func (id NFTokenID) setInto(tkns NFTokens) error { + if len(tkns)+1 > maxCapacity { return fmt.Errorf("%T(len:%v): %T(%v): %v", tkns, len(tkns), id, id, ErrorCapacity) } @@ -44,11 +43,6 @@ func (id NFTokenID) Set(tkns NFTokens) error { return nil } -// Len returns 1. -func (id NFTokenID) Len() int { - return 1 -} - func (id NFTokenID) jsonLen() int { return jsonlen.Uint64(uint64(id)) } diff --git a/fat1/nftokenidrange.go b/fat1/nftokenidrange.go index e506f04..50440a4 100644 --- a/fat1/nftokenidrange.go +++ b/fat1/nftokenidrange.go @@ -25,17 +25,18 @@ package fat1 import ( "encoding/json" "fmt" + "strconv" + "strings" "github.com/Factom-Asset-Tokens/fatd/internal/jsonlen" ) -// NFTokenIDRange represents a contiguous range of NFTokenIDs. -type NFTokenIDRange struct { +type nfTokenIDRange struct { Min NFTokenID `json:"min"` Max NFTokenID `json:"max"` } -func NewNFTokenIDRange(minMax ...NFTokenID) NFTokenIDRange { +func newNFTokenIDRange(minMax ...NFTokenID) nfTokenIDRange { var min, max NFTokenID if len(minMax) >= 2 { min, max = minMax[0], minMax[1] @@ -45,43 +46,53 @@ func NewNFTokenIDRange(minMax ...NFTokenID) NFTokenIDRange { } else if len(minMax) == 1 { min, max = minMax[0], minMax[0] } - return NFTokenIDRange{Min: min, Max: max} + return nfTokenIDRange{Min: min, Max: max} } -func (idRange NFTokenIDRange) IsJSONEfficient() bool { +func (idRange nfTokenIDRange) IsJSONEfficient() bool { var expandedLen int for id := idRange.Min; id <= idRange.Max; id++ { expandedLen += id.jsonLen() + len(`,`) } return idRange.jsonLen() <= expandedLen } +func (idRange nfTokenIDRange) jsonLen() int { + return len(`{"min":`) + + idRange.Min.jsonLen() + + len(`,"max":`) + + idRange.Max.jsonLen() + + len(`}`) +} -func (idRange NFTokenIDRange) IsStringEfficient() bool { +func (idRange nfTokenIDRange) IsStringEfficient() bool { var expandedLen int for id := idRange.Min; id <= idRange.Max; id++ { expandedLen += id.jsonLen() + len(`,`) } return idRange.strLen() <= expandedLen } +func (idRange nfTokenIDRange) strLen() int { + return idRange.Min.jsonLen() + len(`-`) + idRange.Max.jsonLen() +} -func (idRange NFTokenIDRange) Len() int { +func (idRange nfTokenIDRange) Len() int { return int(idRange.Max - idRange.Min + 1) } -func (idRange NFTokenIDRange) Set(tkns NFTokens) error { +func (idRange nfTokenIDRange) setInto(tkns NFTokens) error { if len(tkns)+idRange.Len() > maxCapacity { return fmt.Errorf("%T(len:%v): %T(%v): %v", tkns, len(tkns), idRange, idRange, ErrorCapacity) } for id := idRange.Min; id <= idRange.Max; id++ { - if err := id.Set(tkns); err != nil { + if err := id.setInto(tkns); err != nil { return err } } return nil } -func (idRange NFTokenIDRange) Valid() error { +func (idRange nfTokenIDRange) Valid() error { if idRange.Len() > maxCapacity { return ErrorCapacity } @@ -91,9 +102,7 @@ func (idRange NFTokenIDRange) Valid() error { return nil } -type nfTokenIDRange NFTokenIDRange - -func (idRange NFTokenIDRange) String() string { +func (idRange nfTokenIDRange) String() string { if !idRange.IsStringEfficient() { ids := idRange.Slice() return fmt.Sprintf("%v", ids) @@ -101,7 +110,7 @@ func (idRange NFTokenIDRange) String() string { return fmt.Sprintf("%v-%v", idRange.Min, idRange.Max) } -func (idRange NFTokenIDRange) MarshalJSON() ([]byte, error) { +func (idRange nfTokenIDRange) MarshalJSON() ([]byte, error) { if err := idRange.Valid(); err != nil { return nil, err } @@ -109,11 +118,22 @@ func (idRange NFTokenIDRange) MarshalJSON() ([]byte, error) { ids := idRange.Slice() return json.Marshal(ids) } - return json.Marshal(nfTokenIDRange(idRange)) + type n nfTokenIDRange + return json.Marshal(n(idRange)) +} +func (idRange nfTokenIDRange) MarshalText() ([]byte, error) { + if err := idRange.Valid(); err != nil { + return nil, err + } + if !idRange.IsStringEfficient() { + ids := idRange.Slice() + return json.Marshal(ids) + } + return []byte(fmt.Sprintf("%v-%v", idRange.Min, idRange.Max)), nil } // Slice returns a sorted slice of tkns' NFTokenIDs. -func (idRange NFTokenIDRange) Slice() []NFTokenID { +func (idRange nfTokenIDRange) Slice() []NFTokenID { ids := make([]NFTokenID, idRange.Len()) for i := range ids { ids[i] = NFTokenID(i) + idRange.Min @@ -121,8 +141,9 @@ func (idRange NFTokenIDRange) Slice() []NFTokenID { return ids } -func (idRange *NFTokenIDRange) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, (*nfTokenIDRange)(idRange)); err != nil { +func (idRange *nfTokenIDRange) UnmarshalJSON(data []byte) error { + type n nfTokenIDRange + if err := json.Unmarshal(data, (*n)(idRange)); err != nil { return fmt.Errorf("%T: %w", idRange, err) } if err := idRange.Valid(); err != nil { @@ -133,14 +154,20 @@ func (idRange *NFTokenIDRange) UnmarshalJSON(data []byte) error { } return nil } -func (idRange NFTokenIDRange) jsonLen() int { - return len(`{"min":`) + - idRange.Min.jsonLen() + - len(`,"max":`) + - idRange.Max.jsonLen() + - len(`}`) -} -func (idRange NFTokenIDRange) strLen() int { - return idRange.Min.jsonLen() + len(`-`) + idRange.Max.jsonLen() +func (idRange *nfTokenIDRange) UnmarshalText(text []byte) error { + texts := strings.SplitN(string(text), "-", 2) + if len(texts) != 2 { + return fmt.Errorf("invalid range format") + } + min, err := strconv.ParseUint(texts[0], 10, 64) + if err != nil { + return fmt.Errorf("could not parse min: %w", err) + } + max, err := strconv.ParseUint(texts[1], 10, 64) + if err != nil { + return fmt.Errorf("could not parse max: %w", err) + } + idRange.Min, idRange.Max = NFTokenID(min), NFTokenID(max) + return nil } diff --git a/fat1/nftokenmetadata.go b/fat1/nftokenmetadata.go index 7c9799f..9df25f1 100644 --- a/fat1/nftokenmetadata.go +++ b/fat1/nftokenmetadata.go @@ -31,7 +31,7 @@ import ( type NFTokenIDMetadataMap map[NFTokenID]json.RawMessage -type NFTokenMetadata struct { +type nfTokenMetadata struct { Tokens NFTokens `json:"ids"` Metadata json.RawMessage `json:"metadata,omitempty"` } @@ -84,13 +84,13 @@ func (m NFTokenIDMetadataMap) MarshalJSON() ([]byte, error) { tkns = make(NFTokens) metadataNFTokens[string(metadata)] = tkns } - if err := tknID.Set(tkns); err != nil { + if err := tkns.set(tknID); err != nil { return nil, err } } var i int - tknMs := make([]NFTokenMetadata, len(metadataNFTokens)) + tknMs := make([]nfTokenMetadata, len(metadataNFTokens)) for metadata, tkns := range metadataNFTokens { tknMs[i].Tokens = tkns tknMs[i].Metadata = json.RawMessage(metadata) @@ -100,7 +100,7 @@ func (m NFTokenIDMetadataMap) MarshalJSON() ([]byte, error) { return json.Marshal(tknMs) } -func (m NFTokenIDMetadataMap) IsSubsetOf(tkns NFTokens) error { +func (m NFTokenIDMetadataMap) isSubsetOf(tkns NFTokens) error { if len(m) > len(tkns) { return fmt.Errorf("too many NFTokenIDs") } @@ -113,7 +113,7 @@ func (m NFTokenIDMetadataMap) IsSubsetOf(tkns NFTokens) error { return nil } -func (m NFTokenIDMetadataMap) Set(md NFTokenMetadata) { +func (m NFTokenIDMetadataMap) set(md nfTokenMetadata) { for tknID := range md.Tokens { m[tknID] = md.Metadata } diff --git a/fat1/nftokens.go b/fat1/nftokens.go index 82b5077..2aa9129 100644 --- a/fat1/nftokens.go +++ b/fat1/nftokens.go @@ -26,6 +26,8 @@ import ( "encoding/json" "fmt" "sort" + "strconv" + "strings" ) const MaxCapacity = 4e5 @@ -38,76 +40,41 @@ var ErrorCapacity = fmt.Errorf("NFTokenID max capacity (%v) exceeded", maxCapaci // guarantee uniqueness of NFTokenIDs. type NFTokens map[NFTokenID]struct{} -// NFTokensSetter is an interface implemented by types that can set the -// NFTokenIDs they represent in a given NFTokens. -type NFTokensSetter interface { - // Set the NFTokenIDs in tkns. Return an error if tkns already - // contains one of the NFTokenIDs. - Set(tkns NFTokens) error - // Len returns number of NFTokenIDs that will be set. - Len() int +type nfTokensSetter interface { + setInto(tkns NFTokens) error } -// NewNFTokens returns an NFTokens initialized with ids. If ids contains any -// duplicate NFTokenIDs. -func NewNFTokens(ids ...NFTokensSetter) (NFTokens, error) { - var capacity int - for _, id := range ids { - capacity += id.Len() - } - tkns := make(NFTokens, capacity) - if err := tkns.Set(ids...); err != nil { - return nil, err - } - return tkns, nil -} - -func (tkns NFTokens) Append(newTkns NFTokens) error { - if len(tkns)+len(newTkns) > maxCapacity { +func (tkns NFTokens) setInto(to NFTokens) error { + if len(tkns)+len(to) > maxCapacity { return ErrorCapacity } - if err := tkns.NoIntersection(newTkns); err != nil { - return err - } - for tknID := range newTkns { - tkns[tknID] = struct{}{} + for tknID := range tkns { + if _, ok := to[tknID]; ok { + return errorNFTokenIDIntersection(tknID) + } + to[tknID] = struct{}{} } return nil } -// Set all ids in tkns. Return an error if ids contains any duplicate or -// previously set NFTokenIDs. -func (tkns NFTokens) Set(ids ...NFTokensSetter) error { +func (tkns NFTokens) set(ids ...nfTokensSetter) error { for _, id := range ids { - if err := id.Set(tkns); err != nil { + if err := id.setInto(tkns); err != nil { return err } } return nil } -type ErrorNFTokenIDIntersection NFTokenID +type errorNFTokenIDIntersection NFTokenID -func (id ErrorNFTokenIDIntersection) Error() string { +func (id errorNFTokenIDIntersection) Error() string { return fmt.Sprintf("duplicate NFTokenID: %v", NFTokenID(id)) } -func (tkns NFTokens) NoIntersection(tknsCmp NFTokens) error { - small, large := tkns, tknsCmp - if len(small) > len(large) { - small, large = large, small - } - for tknID := range small { - if _, ok := large[tknID]; ok { - return ErrorNFTokenIDIntersection(tknID) - } - } - return nil -} - -type ErrorMissingNFTokenID NFTokenID +type errorMissingNFTokenID NFTokenID -func (id ErrorMissingNFTokenID) Error() string { +func (id errorMissingNFTokenID) Error() string { return fmt.Sprintf("missing NFTokenID: %v", NFTokenID(id)) } @@ -117,7 +84,7 @@ func (tkns NFTokens) ContainsAll(tknsSub NFTokens) error { } for tknID := range tknsSub { if _, ok := tkns[tknID]; !ok { - return ErrorMissingNFTokenID(tknID) + return errorMissingNFTokenID(tknID) } } return nil @@ -137,46 +104,13 @@ func (tkns NFTokens) Slice() []NFTokenID { return tknsAry } -// MarshalJSON implements the json.Marshaler interface. MarshalJSON will always -// produce the most efficient representation of tkns using NFTokenIDRanges -// over individual NFTokenIDs where appropriate. MarshalJSON will return an -// error if tkns is empty. func (tkns NFTokens) MarshalJSON() ([]byte, error) { if len(tkns) == 0 { - return nil, fmt.Errorf("%T: empty", tkns) + return []byte(`[]`), nil } + tknsAry := tkns.compress(true) - tknsFullAry := tkns.Slice() - - // Compress the tknsAry by replacing contiguous id ranges with an - // NFTokenIDRange. - tknsAry := make([]interface{}, len(tkns)) - idRange := NewNFTokenIDRange(tknsFullAry[0]) - i := 0 - for _, id := range append(tknsFullAry[1:], 0) { - // If this id is contiguous with idRange, expand the range to - // include this id and check the next id. - if id == idRange.Max+1 { - idRange.Max = id - continue - } - // Otherwise, the id is not contiguous with the range, so - // append the idRange and set up a new idRange to start at id. - - // Use the most efficient JSON representation for the idRange. - if idRange.IsJSONEfficient() { - tknsAry[i] = idRange - i++ - } else { - for id := idRange.Min; id <= idRange.Max; id++ { - tknsAry[i] = id - i++ - } - } - idRange = NewNFTokenIDRange(id) - } - - return json.Marshal(tknsAry[:i]) + return json.Marshal(tknsAry) } func (tkns NFTokens) String() string { @@ -184,13 +118,26 @@ func (tkns NFTokens) String() string { return "[]" } - tknsFullAry := tkns.Slice() + tknsAry := tkns.compress(false) + str := "[" + for _, tkn := range tknsAry { + str += fmt.Sprintf("%v,", tkn) + } + return str[:len(str)-1] + "]" +} + +func (tkns NFTokens) compress(forJSON bool) []interface{} { + tknsFullAry := tkns.Slice() // Compress the tknsAry by replacing contiguous id ranges with an - // NFTokenIDRange. + // nfTokenIDRange. tknsAry := make([]interface{}, len(tkns)) - idRange := NewNFTokenIDRange(tknsFullAry[0]) - i := 0 + firstID := tknsFullAry[0] + idRange := nfTokenIDRange{Min: firstID, Max: firstID} + i := 0 // index into tknsAry + // The first id will be placed when the idRange is inserted either as a + // single NFTokenID or as a range. The last id does not get included, + // so append 0. for _, id := range append(tknsFullAry[1:], 0) { // If this id is contiguous with idRange, expand the range to // include this id and check the next id. @@ -201,8 +148,10 @@ func (tkns NFTokens) String() string { // Otherwise, the id is not contiguous with the range, so // append the idRange and set up a new idRange to start at id. - // Use the most efficient JSON representation for the idRange. - if idRange.IsStringEfficient() { + // Use the most efficient JSON or String representation for the + // idRange. + if (forJSON && idRange.IsJSONEfficient()) || + (!forJSON && idRange.IsStringEfficient()) { tknsAry[i] = idRange i++ } else { @@ -211,13 +160,9 @@ func (tkns NFTokens) String() string { i++ } } - idRange = NewNFTokenIDRange(id) + idRange = nfTokenIDRange{Min: id, Max: id} } - str := "[" - for _, tkn := range tknsAry[:i] { - str += fmt.Sprintf("%v,", tkn) - } - return str[:len(str)-1] + "]" + return tknsAry[:i] } func (tkns *NFTokens) UnmarshalJSON(data []byte) error { @@ -225,14 +170,13 @@ func (tkns *NFTokens) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &tknsJSONAry); err != nil { return fmt.Errorf("%T: %w", tkns, err) } - if len(tknsJSONAry) == 0 { - return fmt.Errorf("%T: empty", tkns) + if *tkns == nil { + *tkns = make(NFTokens, len(tknsJSONAry)) } - *tkns = make(NFTokens, len(tknsJSONAry)) for _, data := range tknsJSONAry { - var ids NFTokensSetter + var ids nfTokensSetter if data[0] == '{' { - var idRange NFTokenIDRange + var idRange nfTokenIDRange if err := idRange.UnmarshalJSON(data); err != nil { return fmt.Errorf("%T: %w", tkns, err) } @@ -244,10 +188,46 @@ func (tkns *NFTokens) UnmarshalJSON(data []byte) error { } ids = id } - if err := ids.Set(*tkns); err != nil { + if err := tkns.set(ids); err != nil { return fmt.Errorf("%T: %w", tkns, err) } } return nil } + +func (tkns *NFTokens) UnmarshalText(text []byte) error { + if len(text) < 2 || text[0] != '[' || text[len(text)-1] != ']' { + return fmt.Errorf("invalid format") + } + text = []byte(strings.Trim(string(text), "[]")) + + texts := strings.Split(string(text), ",") + + if *tkns == nil { + *tkns = make(NFTokens, len(texts)) + } + + for _, text := range texts { + var ids nfTokensSetter + var idRange nfTokenIDRange + err := idRange.UnmarshalText([]byte(text)) + if err == nil { + if err := idRange.Valid(); err != nil { + return err + } + ids = idRange + } else { + + tknID, err := strconv.ParseInt(texts[0], 10, 64) + if err != nil { + return err + } + ids = NFTokenID(tknID) + } + if err := tkns.set(ids); err != nil { + return err + } + } + return nil +} diff --git a/fat1/transaction.go b/fat1/transaction.go index e789bf8..95c134a 100644 --- a/fat1/transaction.go +++ b/fat1/transaction.go @@ -38,11 +38,11 @@ const Type = fat.TypeFAT1 // transaction or a coinbase transaction depending on the Inputs and the // RCD/signature pair. type Transaction struct { - Inputs AddressNFTokensMap `json:"inputs"` - Outputs AddressNFTokensMap `json:"outputs"` - TokenMetadata NFTokenIDMetadataMap `json:"tokenmetadata,omitempty"` + Inputs AddressNFTokensMap `json:"inputs"` + Outputs AddressNFTokensMap `json:"outputs"` - Metadata json.RawMessage `json:"metadata,omitempty"` + TokenMetadata NFTokenIDMetadataMap `json:"tokenmetadata,omitempty"` + Metadata json.RawMessage `json:"metadata,omitempty"` Entry factom.Entry `json:"-"` } @@ -53,10 +53,7 @@ func NewTransaction(e factom.Entry, idKey *factom.Bytes32) (Transaction, error) return t, err } - if err := t.Inputs.NoAddressIntersection(t.Outputs); err != nil { - return t, fmt.Errorf("Inputs and Outputs intersect: %w", err) - } - if err := t.Inputs.NFTokenIDsConserved(t.Outputs); err != nil { + if err := t.Inputs.nfTokenIDsConserved(t.Outputs); err != nil { return t, fmt.Errorf("Inputs and Outputs mismatch: %w", err) } @@ -66,7 +63,7 @@ func NewTransaction(e factom.Entry, idKey *factom.Bytes32) (Transaction, error) if len(t.Inputs) != 1 { return t, fmt.Errorf("invalid coinbase transaction") } - if err := t.TokenMetadata.IsSubsetOf( + if err := t.TokenMetadata.isSubsetOf( t.Inputs[fat.Coinbase()]); err != nil { return t, fmt.Errorf("%T.TokenMetadata: %w", t, err) } diff --git a/internal/db/nftokens/nftokens.go b/internal/db/nftokens/nftokens.go index f1bf425..eca5c25 100644 --- a/internal/db/nftokens/nftokens.go +++ b/internal/db/nftokens/nftokens.go @@ -236,9 +236,11 @@ func SelectByOwner(conn *sqlite.Conn, adr *factom.FAAddress, if !hasRow { break } - if err := nfTkns.Set(fat1.NFTokenID(stmt.ColumnInt64(0))); err != nil { - panic(err) + colVal := stmt.ColumnInt64(0) + if colVal < 0 { + panic("negative NFTokenID") } + nfTkns[fat1.NFTokenID(colVal)] = struct{}{} } return nfTkns, nil } diff --git a/internal/srv/methods.go b/internal/srv/methods.go index 536756d..56a2d75 100644 --- a/internal/srv/methods.go +++ b/internal/srv/methods.go @@ -155,7 +155,7 @@ func getTransactions(getEntry bool) jsonrpc2.MethodFunc { // Lookup Txs var nfTkns fat1.NFTokens if params.NFTokenID != nil { - nfTkns, _ = fat1.NewNFTokens(params.NFTokenID) + nfTkns = fat1.NFTokens{*params.NFTokenID: struct{}{}} } entries, err := entries.SelectByAddress(chain.Conn, params.StartHash, params.Addresses, nfTkns, From 17ac008108573655a5a2be40d444881f59272dbe Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 23 Oct 2019 13:30:50 -0800 Subject: [PATCH 107/124] test(fat): Update Issuance tests --- fat/issuance.go | 7 +- fat/issuance_test.go | 425 +++++++++++++++++++++++-------------------- fat/type.go | 6 +- 3 files changed, 236 insertions(+), 202 deletions(-) diff --git a/fat/issuance.go b/fat/issuance.go index 35309af..d85925f 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -63,14 +63,13 @@ func NewIssuance(e factom.Entry, idKey *factom.Bytes32) (Issuance, error) { switch i.Type { case TypeFAT0: - if i.Precision > MaxPrecision { - return i, fmt.Errorf( - `invalid "precision": must be less than 18`) + if i.Precision != 0 && i.Precision > MaxPrecision { + return i, fmt.Errorf(`invalid "precision": out of range [0-18]`) } case TypeFAT1: if i.Precision != 0 { return i, fmt.Errorf( - `invalid "precision": not allowed for %v`, i.Type) + `invalid "precision": not allowed for FAT-1`) } default: return i, fmt.Errorf(`invalid "type": %v`, i.Type) diff --git a/fat/issuance_test.go b/fat/issuance_test.go index bf69c54..8a8186d 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -27,7 +27,9 @@ import ( "testing" "github.com/Factom-Asset-Tokens/factom" + "github.com/Factom-Asset-Tokens/fatd/fat103" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var coinbaseAddressStr = "FA1zT4aFpEvcnPqPCigB3fvGu4Q4mTXY22iiuV69DqE1pNhdF2MC" @@ -37,222 +39,259 @@ func TestCoinbase(t *testing.T) { assert.Equal(t, coinbaseAddressStr, a.String()) } -var issuanceTests = []struct { +var ( + issuerKey = issuerSecret.ID1Key() + issuerSecret = func() factom.SK1Key { + a, _ := factom.GenerateSK1Key() + return a + }() + chainID = factom.Bytes32{1: 1, 2: 2} +) + +type issuanceTest struct { Name string Error string - IssuerKey factom.ID1Key - Issuance -}{{ - Name: "valid", - IssuerKey: issuerKey, - Issuance: validIssuance(), -}, { - Name: "valid (omit symbol)", - IssuerKey: issuerKey, - Issuance: omitFieldIssuance("symbol"), -}, { - Name: "valid (omit name)", - IssuerKey: issuerKey, - Issuance: omitFieldIssuance("name"), -}, { - Name: "valid (omit metadata)", - IssuerKey: issuerKey, - Issuance: omitFieldIssuance("metadata"), -}, { - Name: "invalid JSON (unknown field)", - Error: `*fat.Issuance: unexpected JSON length`, - IssuerKey: issuerKey, - Issuance: setFieldIssuance("invalid", 5), -}, { - Name: "invalid JSON (invalid type)", - Error: `*fat.Issuance: *fat.Type: expected JSON string`, - IssuerKey: issuerKey, - Issuance: invalidIssuance("type"), -}, { - Name: "invalid JSON (invalid supply)", - Error: `*fat.Issuance: json: cannot unmarshal array into Go struct field issuance.supply of type int64`, - IssuerKey: issuerKey, - Issuance: invalidIssuance("supply"), -}, { - Name: "invalid JSON (invalid symbol)", - Error: `*fat.Issuance: json: cannot unmarshal array into Go struct field issuance.symbol of type string`, - IssuerKey: issuerKey, - Issuance: invalidIssuance("symbol"), -}, { - Name: "invalid JSON (nil)", - Error: `unexpected end of JSON input`, - IssuerKey: issuerKey, - Issuance: issuanceFromRaw(nil), -}, { - Name: "invalid data (type)", - Error: `*fat.Issuance: *fat.Type: invalid format`, - IssuerKey: issuerKey, - Issuance: setFieldIssuance("type", "invalid"), -}, { - Name: "invalid data (type omitted)", - Error: `*fat.Issuance: unexpected JSON length`, - IssuerKey: issuerKey, - Issuance: omitFieldIssuance("type"), -}, { - Name: "invalid data (supply: 0)", - Error: `*fat.Issuance: invalid "supply": must be positive or -1`, - IssuerKey: issuerKey, - Issuance: setFieldIssuance("supply", 0), -}, { - Name: "invalid data (supply: -5)", - Error: `*fat.Issuance: invalid "supply": must be positive or -1`, - IssuerKey: issuerKey, - Issuance: setFieldIssuance("supply", -5), -}, { - Name: "invalid data (supply: omitted)", - Error: `*fat.Issuance: invalid "supply": must be positive or -1`, - IssuerKey: issuerKey, - Issuance: omitFieldIssuance("supply"), -}, { - Name: "invalid ExtIDs (timestamp)", - Error: `timestamp salt expired`, - IssuerKey: issuerKey, - Issuance: func() Issuance { - i := validIssuance() - i.ExtIDs[0] = factom.Bytes("10") - return i + Entry factom.Entry + IssuerKey *factom.ID1Key +} + +var issuanceTests = []issuanceTest{{ + Name: "valid", + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: 100000, + Precision: 3, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e }(), }, { - Name: "invalid ExtIDs (length)", - Error: `invalid number of ExtIDs`, - IssuerKey: issuerKey, - Issuance: func() Issuance { - i := validIssuance() - i.ExtIDs = append(i.ExtIDs, factom.Bytes{}) - return i + Name: "valid (omit symbol)", + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: 100000, + Precision: 3, + Metadata: json.RawMessage(`"memo"`), + + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e }(), }, { - Name: "invalid RCD hash", - Error: `invalid RCD`, - Issuance: validIssuance(), -}} - -func TestIssuance(t *testing.T) { - for _, test := range issuanceTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - i := test.Issuance - key := test.IssuerKey - err := i.Validate((*factom.Bytes32)(&key)) - if len(test.Error) == 0 { - assert.NoError(err) - } else { - assert.EqualError(err, test.Error) - } - }) - } -} - -func validIssuanceEntryContentMap() map[string]interface{} { - return map[string]interface{}{ - "type": "FAT-0", - "supply": int64(100000), - "symbol": "TEST", - "metadata": []int{0}, - } -} - -func validIssuance() Issuance { - return issuanceFromRaw(marshal(validIssuanceEntryContentMap())) -} + Name: "valid (omit metadata)", + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: 100000, + Precision: 3, + Symbol: "test", -var issuerSecret = func() factom.SK1Key { - a, _ := factom.GenerateSK1Key() - return a -}() -var issuerKey = issuerSecret.ID1Key() + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), +}, { + Name: "valid (type 1)", + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT1, + Supply: 100000, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), -func issuanceFromRaw(content factom.Bytes) Issuance { - e := Entry{factom.Entry{ - ChainID: new(factom.Bytes32), - Content: content, - }} - e.Sign(issuerSecret) - id1Key := issuerSecret.ID1Key() - i, _ := NewIssuance(e.Entry, (*factom.Bytes32)(&id1Key)) - return i -} + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), +}, { + Name: "valid (unlimited)", + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: -1, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), -func invalidIssuance(field string) Issuance { - return setFieldIssuance(field, []int{0}) -} + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), +}, { + Name: "invalid JSON (unknown field)", + Error: `*fat.Issuance: unexpected JSON length`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: -1, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), -func omitFieldIssuance(field string) Issuance { - m := validIssuanceEntryContentMap() - delete(m, field) - return issuanceFromRaw(marshal(m)) -} + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + var m map[string]interface{} + if err := json.Unmarshal(e.Content, &m); err != nil { + panic(err) + } + m["newfield"] = 5 + content, err := json.Marshal(m) + if err != nil { + panic(err) + } + e.Content = content + return fat103.Sign(e, issuerSecret) + }(), +}, { + Name: "invalid (supply<-1)", + Error: `invalid "supply": must be positive or -1`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT1, + Supply: -10, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + Precision: 3, -func setFieldIssuance(field string, value interface{}) Issuance { - m := validIssuanceEntryContentMap() - m[field] = value - return issuanceFromRaw(marshal(m)) -} + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), +}, { + Name: "invalid (supply=0)", + Error: `invalid "supply": must be positive or -1`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT1, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + Precision: 3, -func marshal(v map[string]interface{}) []byte { - data, err := json.Marshal(v) - if err != nil { - panic(err) - } - return data -} + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), +}, { + Name: "invalid (fat1, precision)", + Error: `invalid "precision": not allowed for FAT-1`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT1, + Supply: -1, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + Precision: 3, -var issuanceMarshalEntryTests = []struct { - Name string - Error string - Issuance -}{{ - Name: "valid", - Issuance: newIssuance(), + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), }, { - Name: "valid (metadata)", - Issuance: func() Issuance { - i := newIssuance() - i.Metadata = json.RawMessage(`{"memo":"new token"}`) - return i + Name: "invalid (fat0, precision>18 )", + Error: `invalid "precision": out of range [0-18]`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: -1, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + Precision: 30, + + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e }(), }, { - Name: "invalid data", - Error: `json: error calling MarshalJSON for type *fat.Issuance: invalid "type": invalid fat.Type: 1000`, - Issuance: func() Issuance { - i := newIssuance() - i.Type = 1000 - return i + Name: "invalid (type)", + Error: `invalid "type": FAT-1000000000`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: 1000000000, + Supply: -1, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + Precision: 3, + + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e }(), }, { - Name: "invalid metadata JSON", - Error: `json: error calling MarshalJSON for type *fat.Issuance: json: error calling MarshalJSON for type json.RawMessage: invalid character 'a' looking for beginning of object key string`, - Issuance: func() Issuance { - i := newIssuance() - i.Metadata = json.RawMessage("{asdf") - return i + Name: "invalid (issuer key)", + Error: `ExtIDs[1]: unexpected or duplicate RCD Hash`, + IssuerKey: new(factom.ID1Key), + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: 100000, + Precision: 3, + Symbol: "test", + Metadata: json.RawMessage(`"memo"`), + + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e }(), }} -func TestIssuanceMarshalEntry(t *testing.T) { - for _, test := range issuanceMarshalEntryTests { - t.Run(test.Name, func(t *testing.T) { - assert := assert.New(t) - i := test.Issuance - err := i.PopulateEntry(&issuerSecret) - if len(test.Error) == 0 { - assert.NoError(err) - } else { - assert.EqualError(err, test.Error) - } - }) +func TestIssuance(t *testing.T) { + for _, test := range issuanceTests { + test := test + t.Run(test.Name, func(t *testing.T) { testIssuance(t, test) }) } } -func newIssuance() Issuance { - return Issuance{ - Type: TypeFAT0, - Supply: 1000000, - Symbol: "TEST", +func testIssuance(t *testing.T, test issuanceTest) { + assert := assert.New(t) + require := require.New(t) + issuerKey := &issuerKey + if test.IssuerKey != nil { + issuerKey = test.IssuerKey + } + i, err := NewIssuance(test.Entry, (*factom.Bytes32)(issuerKey)) + if len(test.Error) == 0 { + require.NoError(err) + assert.NotEqual(Issuance{}, i) + return } + assert.EqualError(err, test.Error) } diff --git a/fat/type.go b/fat/type.go index 31f9f56..8464c91 100644 --- a/fat/type.go +++ b/fat/type.go @@ -53,11 +53,7 @@ func (t *Type) UnmarshalText(text []byte) error { } func (t Type) MarshalText() ([]byte, error) { - if !t.IsValid() { - return nil, fmt.Errorf("invalid fat.Type: %v", int(t)) - } - - return []byte(fmt.Sprintf("%v%v", format, int(t))), nil + return []byte(fmt.Sprintf("%v%v", format, uint(t))), nil } func (t Type) String() string { From 73bdb766a6e3534f19209cfec6e8035e40748f6b Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 23 Oct 2019 13:31:28 -0800 Subject: [PATCH 108/124] fix(fat103): Correct ExtIDs index in Validate error output --- fat103/validate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fat103/validate.go b/fat103/validate.go index 6c0169c..a6a181b 100644 --- a/fat103/validate.go +++ b/fat103/validate.go @@ -82,7 +82,7 @@ func Validate(e factom.Entry, expected map[factom.Bytes32]struct{}) error { rcdHash := sha256d(rcd) if _, ok := expected[rcdHash]; !ok { return fmt.Errorf( - "ExtIDs[%v]: unexpected or duplicate RCD Hash", i) + "ExtIDs[%v]: unexpected or duplicate RCD Hash", i+1) } delete(expected, rcdHash) From a332a3298d8c4a1f483e32d5bf60273ce5079439 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Wed, 23 Oct 2019 18:38:37 -0800 Subject: [PATCH 109/124] docs(README): Update version details --- CLI.md | 359 ------------------------------------------------------ README.md | 130 +++++++------------- 2 files changed, 42 insertions(+), 447 deletions(-) delete mode 100644 CLI.md diff --git a/CLI.md b/CLI.md deleted file mode 100644 index 1d3cbed..0000000 --- a/CLI.md +++ /dev/null @@ -1,359 +0,0 @@ -# FAT CLI Documentation - -The fat-cli allows users to explore and interact with FAT chains. - -fat-cli can be used to explore FAT chains to get balances, issuance, and -transaction data. It can also be used to send transactions on existing FAT -chains, and issue new FAT-0 or FAT-1 tokens. - -**Chain ID Settings** - -Most sub-commands need to be scoped to a specific FAT chain, identified by a -`--chainid`. Alternatively, this can be specified by using both the `--tokenid` -and `--identity`, which together determine the chain ID. - -**API Settings** - -fat-cli makes use of the fatd, factomd, and factom-walletd JSON-RPC 2.0 APIs -for various operations. Trust in these API endpoints is imperative to secure -operation. - -The `--fatd` API is used to explore issuance, transactions, and balances for -existing FAT chains. - -The `--factomd` API is used to submit entries directly to the Factom -blockchain, as well as for checking EC balances, chain existence, and identity -keys. - -The `--walletd` API is used to access private keys for FA and EC addresses. To -avoid use of factom-walletd, use private Fs or Es keys directly on the CLI -instead. - -If `--debug` is set, all fatd and factomd API calls will be printed to stdout. -API calls to factom-walletd are omitted to avoid leaking private key data. - -**Offline Mode** - -For increased security to protect private keys, it is possible to run fat-cli -such that it makes no network calls when generating Factom entries for FAT -transactions or token issuance. - -Use `--curl` to skip submitting the entry directly to Factom, and instead print -out the curl commands for committing and revealing the entry. These curl -commands contain the encoded signed data and may be safely copied to, and run -from, a computer with access to factomd. - -Use `--force` to skip all sanity checks that involve API calls out factomd or -fatd. As a result, this may result in generating a Factom Entry that is invalid -for Factom or FAT, but may still use up Entry Credits to submit. - -Use private keys for `--ecadr` and --input directly to avoid any network calls -to factom-walletd. - -**Entry Credits** -Making FAT transactions or issuing new FAT tokens requires creating entries on -the Factom blockchain. Creating Factom entries costs Entry Credits. Entry -Credits have a relatively fixed price of about $0.001 USD. Entry Credits can be -obtained by burning Factoids which can be done using the official factom-cli. -FAT transactions normally cost 1 EC. The full FAT Token Issuance process -normally costs 12 EC. - -### CLI Completion -After installing fat-cli in some permanent location in your PATH. Use ---installcompletion to install CLI completion for Bash, Zsh, or Fish. This -simply adds a single line to your `~/.bash_profile` (or shell equivalent), -which can be removed with --uninstallcompletion. You must re-open your shell -before completion changes take effect. - -No other programs or files need to be installed because fat-cli is also its own -completion program. If fat-cli is envoked by the completion system, it returns -completions for the currently typed arguments. - -If the `--fatd` endpoint is available, Token Chain IDs can be completed based -on the chains that fatd is tracking. - -If the `--walletd` endpoint is available, then all FA and EC addresses can be -completed based on the addresses saved by factom-walletd. - -Since both of these completion flags require successful API calls, any required -API related flags must already be supplied before completion for Token Chain -IDs, FA or EC addresses can succeed. Otherwise, if the default settings are -incorrect, generating completion suggestions will fail silently. Note that ---timeout is ignored as a very short timeout is always used to avoid noticeable -blocking when generating completion suggestions. - - -# Flags - -## General - -### `--debug` - -Print fatd and factomd API calls - -### `--verbose` - -Print verbose details about sanity check and other operations - -### `--help` - -Get help with using the CLI. Can follow any command or subcommand to get -detailed help. - - - -## Network & Auth - -### `--fatd` - -Fatd URL - -scheme://host:port for fatd (default `localhost:8078`) - -### `--fatdpass` - -Basic HTTP Auth Password for fatd - -### `--fatduser` - -Basic HTTP Auth User for fatd - -### `--factomd` - -Factomd URL - -scheme://host:port for factomd (default `localhost:8088`) - -### `--factomdpass` - -Basic HTTP Auth Password for factomd - -### `--factomduser` - -Basic HTTP Auth User for factomd - -### `--walletd` - -Factom Walletd URL - -scheme://host:port for factom-walletd (default `localhost:8089`) - -### `--walletduser` - -Basic HTTP Auth User for factom-walletd - -### `--walletdpass` - -Basic HTTP Auth Password for factom-walletd - -### `--timeout` - -Timeout for all API requests (i.e. `10s`, `1m`) (default `3s`) - - - -## Tokens - -### `--chainid` - -The 32 Byte Factom Chain ID of the token to get data for. The token chain ID -can be calculated from `--identity` & `--tokenid`. Either `--chainid` OR -`--identity` & `--tokenid` should be supplied - -### `--identity` - -Token Issuer Identity Chain ID of a FAT token. - -### `--tokenid` - -The Token ID string of a FAT chain. - - - -# Commands - -## `get` - -Retrieve data about FAT tokens or a specific FAT token - -``` - fat-cli get [subcommand] [flags] -``` - -### Subcommands - -#### `chains` - -Get information about tokens and token chains. Print a list including the Chain -ID, Issuer identity chain ID, and token ID of each token currently tracked by -the fat daemon. - -``` -fat-cli get chains -``` - -If the optional `` argument is supplied the info for that specific -chain will be returned including statistics. - - -#### `balance` - -Get the balance of a Factoid address for a token - -``` -fat-cli get balance --chainid -``` - -#### `transactions` - -Get transaction history and specific transactions belonging to a specific FAT -token. - - -``` -fat-cli get transactions --chainid [--starttx ] - [--page ] [--limit ] [--order <"asc" | "desc">] - [--address [--address ]... [--to] [--from]] - [--nftokenid ] -``` - -- `--address` - Add to the set of addresses to lookup txs for -- `--from` - Request only txs FROM the given `--address` set -- `--to` - Request only txs TO the given --address set -- `--limit` - Limit of returned txs (default `10`) -- `--nftokenid` - Request only txs involving this NF Token ID -- `--order` - Order of returned txs (`asc`|`desc`, default `asc`) -- `--page` - Page of returned txs (default `1`) -- `--starttx` - Entryhash of tx to start indexing from - - - -## `issue` - -Issue a new FAT-0 or FAT-1 token chain. - -Issuing a new FAT token chain is a two step process but only requires a single command. - -First, the Token Chain must be created with the correct Name IDs on the Factom -Blockchain. So both --tokenid and --identity are required and use of --chainid -is not allowed for this step. If the Chain Creation Entry has already been -submitted then this step is skipped over. - -Second, the Token Initialization Entry must be added to the Token Chain. The -Token Initialization Entry must be signed by the SK1 key corresponding to the -ID1 key declared in the --identity chain. Both --type and --supply are -required. The --supply must be positive or -1 for an unlimited supply of -tokens. - -Note that publishing a Token Initialization Entry is an immutable operation. -The protocol does not permit altering the Token Initialization Entry in any -way. - -Sanity Checks - Prior to composing the Chain Creation or Token Initialization Entry, a - number of calls to fatd and factomd are made to ensure that the token - can be issued. These checks are skipped if --force is used. - - - Skip Chain Creation Entry if already submitted. - - The token has not already been issued. - - The --identity chain exists. - - The --sk1 key corresponds to the --identity's id1 key. - - The --ecadr has enough ECs to pay for all entries. - -Identity Chain - FAT token chains may only be issued by an entity controlling the - sk1/id1 key established by the Identity Chain pointed to by the FAT - token chain. An Identity Chain and the associated keys can be created - using the factom-identity-cli. - - https://github.com/PaulBernier/factom-identity-cli - -Entry Credits - Creating entries on the Factom blockchain costs Entry Credits. The full - Token Issuance process normally costs 12 ECs. You must specify a funded - Entry Credit address with --ecadr, which may be either a private Es - address, or a pubilc EC address that can be fetched from - factom-walletd. - -**Usage** - -``` - fat-cli issue --ecadr --sk1 - --identity --tokenid - --type <"FAT-0" | "FAT-1"> --supply [--metadata ] [flags] -``` - -``` -Flags: - --curl Do not submit Factom entry; print curl commands - -e, --ecadr EC or Es address to pay for entries - --force Skip sanity checks for balances, chain status, and sk1 key - -h, --help help for issue - -m, --metadata JSON JSON metadata to include in tx - --sk1 sk1 Secret Identity Key 1 to sign entry - --supply int Max Token supply, use -1 for unlimited - --symbol string Optional abbreviated token symbol - --type <"FAT-0" | "FAT-1"> Token standard to use -``` - -**Example Commands** - -Initialize a FAT-0 token called "test" with a maximum supply of 100,000 units: - -``` -fat-cli issue --ecadr EC3cQ1QnsE5rKWR1B5mzVHdTkAReK5kJwaQn5meXzU9wANyk7Aej --sk1 sk1... --identity 888888a37cbf303c0bfc8d0cc7e77885c42000b757bd4d9e659de994477a0904 --tokenid test --type "FAT-0" --supply 100000 -``` - -Initialize a FAT-1 token called "test-nft" with an unlimited supply: -``` -fat-cli issue --ecadr EC3cQ1QnsE5rKWR1B5mzVHdTkAReK5kJwaQn5meXzU9wANyk7Aej --sk1 sk1... --identity 888888a37cbf303c0bfc8d0cc7e77885c42000b757bd4d9e659de994477a0904 --tokenid test-nft --type "FAT-1" --supply -1 -``` - -## `transact` - -Send or distribute FAT-0 or FAT-1 tokens. - -Submitting a FAT transaction involves submitting a signed transaction entry to -the given FAT Token Chain - -**Usage** - -``` -fat-cli transact --input --output [--metadata ] [--sk1 ] [--ecadr ] [--curl] [--force] -``` - -- `--input` - An input to the transaction. May be specified multiple times. For - example a FAT-0 tx input could look like -`FA3SjebEevRe964p4tQ6eieEvzi7puv9JWF3S3Wgw2v3WGKueL3R:150`, a FAT-1 tx input -could look like -`FA3SjebEevRe964p4tQ6eieEvzi7puv9JWF3S3Wgw2v3WGKueL3R:[1,2,5-100]`. It is -allowed to use private Factoid keys instead of public ones if `--walletd` is -not specified or available. -- `--output` - An output to the transaction. May be specified multiple times. - Follows the same form as `--input` except no private Factoid keys are -permitted. -- `--metadata` - JSON compliant metadata to attach to the transaction -- `--sk1` - The SK1 Private identity key belonging to the issuer of the token. - Required for coinbase transactions. -- `--ecadr` - EC or Es address to pay for the chain creation and token issuance - entries, if `--factomd` is specified. -- `--curl` - Do not submit the Factom entry; print curl commands instead! -- `--force` - Skip sanity checks for balances, chain status, and sk1 key - -**Example Commands** - -FAT-0 Coinbase Transaction - -Use `--sk1` and `--output` for coinbase transactions; no `--input` is specified as the coinbase input address is always `FA1zT4aFpEvcnPqPCigB3fvGu4Q4mTXY22iiuV69DqE1pNhdF2MC`, the public address that corresponds to a private key of all zeroes. This creates 100 new tokens and sends them to the the `FA2gCm...` address. - -``` -fat-cli transact fat0 --output FA2gCmih3PaSYRVMt1jLkdG4Xpo2koebUpQ6FpRRnqw5FfTSN2vW:100 --sk1 sk1... --ecadr EC3cQ1QnsE5rKWR1B5mzVHdTkAReK5kJwaQn5meXzU9wANyk7Aej -``` - -FAT-1 Transaction - -This moves the token with an id of 10 from `FA2gCm...` to `FA3j68...`. - -``` -fat-cli transact fat1 --input FA2gCmih3PaSYRVMt1jLkdG4Xpo2koebUpQ6FpRRnqw5FfTSN2vW:[10] --output FA3j68XNwKwvHXV2TKndxPpyCK3KrWTDyyfxzi8LwuM5XRuEmhy6:[10] --ecadr EC3cQ1QnsE5rKWR1B5mzVHdTkAReK5kJwaQn5meXzU9wANyk7Aej -``` \ No newline at end of file diff --git a/README.md b/README.md index af36f6e..ad6acdf 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,26 @@ ![](https://png.icons8.com/ios-glyphs/200/5ECCDD/octahedron.png)![](https://png.icons8.com/color/64/3498db/golang.png) +This repo contains the Golang reference implementation of the Factom Asset +Tokens protocol. This repo provides two executables for interacting with FAT +chains, as well as several Golang packages for use by external programs. + # fatd - Factom Asset Token Daemon - Alpha -A daemon written in Golang that maintains the current state of Factom Asset -Tokens (FAT) token chains. The daemon provides a JSON-RPC 2.0 API for accessing -data about token chains. +A daemon written in Golang that discovers new Factom Asset Tokens chains and +maintains their current state. The daemon provides a JSON-RPC 2.0 API for +accessing data about token chains. ### fat-cli A CLI for creating new FAT chains, as well as exploring and making transactions -on existing FAT chains. See the output from `fat-cli --help` and see -[CLI.md](CLI.md) for more information. +on existing FAT chains. See the output from `fat-cli --help` for more +information. ## Development Status -The FAT protocol and this implementation is still in Alpha. That means that we -are still testing and making changes to the protocol and the implementation. -The on-chain data protocol is relatively stable but this implementation and the -database schema is not. - -So long as the major version is v0 everything is subject to potential change. - -At times new v0 releases may require you to rebuild your fatd.db database from -scratch, but this will be minimized after the next major release due to an -improved migration framework and database validation on startup. +This implementation is now at a v1.0 release, which means that the public APIs, +both Golang and the JSON-RPC endpoint are version locked. This also means that +we believe the code to be stable and secure. That being said, the FAT system +and really the entire blockchain industry is experimental. Please help us improve the code and the protocol by trying to break things and reporting bugs! Thank you! @@ -32,6 +30,7 @@ reporting bugs! Thank you! Pre-compiled binaries for Linux, Windows, and Mac x86\_64 systems can be found on the [releases page.](https://github.com/Factom-Asset-Tokens/fatd/releases/) +However, building from source is very easy on most platforms. ## Install with Docker 🐳 @@ -58,22 +57,15 @@ $ docker run -d --name=fatd --network=host -v "fatd_db:/fatd.db" fatd [fatd opti #### Build Dependencies This project uses SQLite3 which uses [CGo](https://blog.golang.org/c-go-cgo) to -dynamically link to the SQLite3 C shared libraries to the `fatd` Golang binary. -CGo requires that GCC be available on your system. +compile and statically link the SQLite3 C libraries to the `fatd` Golang +binary. CGo requires that GCC be available on your system. The following dependencies are required to build `fatd` and `fat-cli`. - [Golang](https://golang.org/) 1.13 or later. The latest official release of Golang is always recommended. -- [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) is used by - the code generation used in the `./factom` package. The code generation step -is not required as the latest generated code is already checked into the -repository. Sometimes `make` will try to run this step which will result in an -error if `goimprots` is not installed. - [GNU GCC](https://gcc.gnu.org/) is used by [CGo](https://blog.golang.org/c-go-cgo) to link to the SQLite3 shared libraries. -- [SQLite3](https://sqlite.org/index.html) is the database `fatd` uses to save - state. - [Git](https://git-scm.com/) is used to clone the project and is used by `go build` to pull some dependencies. - [GNU Bash](https://www.gnu.org/software/bash/) is used by a small script @@ -131,73 +123,43 @@ when `fatd` or `fat-cli` is updated. -## Running +## Getting started + +The Daemon needs a connection to `factomd`'s API. This defaults to +`http://localhost:8088` and can be specified with `-s`. + Start the daemon from the command line: ``` -$ ./fatd -INFO Fatd Version: r155.c812dd1 pkg=main +INFO Fatd Version: v0.6.0.r110.g73bdb76 pkg=main +INFO Loading chain databases from /home/aslevy/.fatd/mainnet/... pkg=engine INFO State engine started. pkg=main +INFO Listening on :8078... pkg=srv INFO JSON RPC API server started. pkg=main INFO Factom Asset Token Daemon started. pkg=main -INFO Syncing from block 183396 to 183520... pkg=engine -INFO Synced. pkg=engine -``` - -### Exiting -To tell `fatd` to safely exit send a `SIGINT`. From most shells this can be -done by simply pressing `CTRL`+`c`. -``` -INFO Synced. pkg=engine -^CINFO SIGINT: Shutting down now. pkg=main -INFO Factom Asset Token Daemon stopped. pkg=main -INFO JSON RPC API server stopped. pkg=main -INFO State engine stopped. pkg=main -$ +INFO Searching for new FAT chains from block 163181 to 215642... pkg=engine +INFO Tracking new FAT chain: b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb pkg=engine +INFO Syncing... chain=b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb +INFO Synced. chain=b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb ``` +At this point `fatd` has synced the first ever created FAT chain on mainnet, +which is for testing purposes. It is continuing to scan for all valid FAT +chains so it can track them. +If you know the chain id of a FAT chain you are interested in, you can fast +sync it by using the `-whitelist` flag. -## Startup Flags & Options +The daemon can be stopped and restarted and syncing will resume from the latest +point. By default the database directory is `~/.fatd/`. It can be specified +with `-dbdir`. -Control how fatd runs using additional options at startup. For example: +Once the JSON RPC API is started, `fat-cli` can be used to query about synced +chains, transactions and balances. -```bash -./fatd -debug -dbpath /home/ubuntu/mycustomfolder -``` +For a complete an up-to-date list of flags & options please see `fatd -h` and +`fat-cli -h`. - - -| Name | Description | Validation | Default | -| ----------------- | ------------------------------------------------------------ | ------------------------ | ------------------------- | -| `startscanheight` | The Factom block height to begin scanning for new FAT chains and transactions | Positive Integer | 0 | -| `debug` | Enable debug mode for extra information during runtime. No value needed. | - | - | -| `dbpath` | Specify the path to use as fatd's sqlite database. | Valid system path | Current working directory | -| `ecpub` | The public Entry Credit address used to pay for submitting transactions | Valid EC address | - | -| `apiaddress` | What port string the FAT daemon RPC will be bound to | String | `:8078` | -| `apiusername` | The basic HTTP auth username to require for connections to fatd | String | - | -| `apipassword` | The basic HTTP auth username to require for connections to fatd | String | - | -| `apitlscert` | The file path to the TLS certificate file to use for secure conncetions | Valid system path | - | -| `apitlskey` | The file path to the TLS key file to use for secure conncetions | Valid system path | - | -| | | | | -| `s` | The URL of the Factom API host | Valid URL | `localhost:8088` | -| `factomdtimeout` | The timeout in seconds to time out requests to factomd | Integer | 0 | -| `factomduser` | The username of the user for factomd API authentication | String | - | -| `factomdpassword` | The password of the user for factomd API authentication | String | - | -| `factomdcert` | Path to the factomd connection TLS certificate file | Valid system path string | - | -| `factomdtls` | Whether to use TLS on connection to factomd | Boolean | false | -| | | | | -| `w` | The URL of the Factom Wallet Daemon API host | Valid URL | `localhost:8089` | -| `wallettimeout` | The timeout in seconds to time out requests to factomd | Integer | 0 | -| `walletuser` | The username of the user for walletd API authentication | String | - | -| `walletpassword` | The username of the user for walletd API authentication | String | - | -| `walletcert` | Path to the walletd connection TLS certificate file | Valid system path string | - | -| `wallettls` | Whether to use TLS on connection to walletd | Boolean | false | - -For a complete up to date list of flags & options please see `flag/flag.go` - - - -## [FAT CLI Documentation](CLI.md) +### Create a chain, make transactions Interact with the FAT daemon RPC from the command line @@ -211,15 +173,7 @@ Default `http://localhost:8078/v1` - ## Contributing -All PRs should be rebased on the latest `develop` branch commit. - -## Issues - -Please attempt to reproduce the issue using the `-debug` flag. For `fatd`, -please provide the initial output which prints all current settings. -Intermediate `DEBUG Scanning block 187682 for FAT entries.` lines may be -omitted, but please provide the first and last of these lines. +See [CONTRIBUTING.md](./CONTRIBUTING.md) From 4ab630c4f98eeac152f42575afbfea85ddcec9e3 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 24 Oct 2019 21:06:42 -0800 Subject: [PATCH 110/124] fix(flag): Correct flag DEBUG output --- internal/flag/flag.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 868c0fd..cf7e6fc 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -350,9 +350,11 @@ func Validate() { log.Debugf("-dbpath %#v", DBPath) log.Debugf("-apiaddress %#v", APIAddress) + debugPrintln() + log.Debugf("-startscanheight %v ", StartScanHeight) log.Debugf("-factomscanretries %v ", FactomScanRetries) - log.Debugf("-startscaninterval %v ", FactomScanInterval) + log.Debugf("-factomscaninterval %v ", FactomScanInterval) debugPrintln() log.Debugf("-networkid %v", NetworkID) From ca8aac3e158b5a8b0f8c2a02bae911d90488b3ce Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 24 Oct 2019 21:20:17 -0800 Subject: [PATCH 111/124] fix(engine): Mark eblocksWG done properly on error --- internal/engine/engine.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/engine/engine.go b/internal/engine/engine.go index c7dcee8..aace225 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -231,6 +231,7 @@ func engine(ctx context.Context, done chan struct{}) { log.Errorf("ChainID(%v): %v", eb.ChainID, err) } + eblocksWG.Done() return } eblocksWG.Done() From 5088b907b27e7733d3ba2dcf9b9c6da1e4a6aa4a Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 24 Oct 2019 21:21:13 -0800 Subject: [PATCH 112/124] fix(engine): Don't apply pending entries to not yet issued chains --- internal/engine/process.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/engine/process.go b/internal/engine/process.go index f239a80..ead688f 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -142,8 +142,8 @@ func Process(ctx context.Context, dbKeyMR *factom.Bytes32, eb factom.EBlock) err func ProcessPending(ctx context.Context, es ...factom.Entry) error { chain := Chains.get(es[0].ChainID) - // We can only apply pending entries to tracked chains. - if !chain.IsTracked() { + // We can only apply pending entries to Issued chains. + if !chain.IsIssued() { return nil } From 67c6551d0e20985396a8a9c59be2e5723677214f Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 24 Oct 2019 21:22:07 -0800 Subject: [PATCH 113/124] fix(cli): Print chain stats properly --- cli/getchains.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/cli/getchains.go b/cli/getchains.go index a484669..1632e6e 100644 --- a/cli/getchains.go +++ b/cli/getchains.go @@ -25,6 +25,7 @@ package main import ( "context" "fmt" + "time" "github.com/Factom-Asset-Tokens/factom" "github.com/Factom-Asset-Tokens/fatd/api" @@ -128,27 +129,28 @@ Token ID: %q } func printStats(chainID *factom.Bytes32, stats api.ResultGetStats) { - fmt.Printf(`Chain ID: %v -Issuer Identity Chain ID: %v -Issuance Entry Hash: %v -Token ID: %v -Type: %v -Symbol: %q -Precision: %q -Supply: %v -Ciculating Supply: %v -Burned: %v -Number of Transactions: %v + fmt.Printf(` +Chain ID: %v +Issuer Identity: %v +Issuance Entry: %v +Token ID: %v +Type: %v +Symbol: %q +Precision: %v +Supply: %v +Circulating Supply: %v +Burned: %v +Number of Transactions: %v Issuance Timestamp: %v `, chainID, stats.IssuerChainID, stats.IssuanceHash, stats.TokenID, - stats.Issuance.Type, stats.Issuance.Symbol, + stats.Issuance.Type, stats.Issuance.Symbol, stats.Issuance.Precision, stats.Issuance.Supply, stats.CirculatingSupply, stats.Burned, stats.Transactions, - stats.IssuanceTimestamp) + time.Unix(stats.IssuanceTimestamp, 0)) if stats.LastTransactionTimestamp > 0 { - fmt.Printf("Last Tx Timestamp: %v\n", - stats.LastTransactionTimestamp) + fmt.Printf("Last Tx Timestamp: %v\n", + time.Unix(stats.LastTransactionTimestamp, 0)) } fmt.Println() From 0e8143c4dc15c5088e0b0c481639f50f0dfb3a79 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 24 Oct 2019 21:23:02 -0800 Subject: [PATCH 114/124] fix(cli): Pkg factom update, correct issuance checks --- cli/issue.go | 19 ++++++++++++------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cli/issue.go b/cli/issue.go index 11d8c4c..2851b22 100644 --- a/cli/issue.go +++ b/cli/issue.go @@ -160,7 +160,7 @@ var ( Code: -32009, Message: "Missing Chain Head"} newChainInProcessListErr = jsonrpc2.Error{Message: "new chain in process list"} - first factom.Entry + first = factom.Entry{Content: factom.Bytes{}} chainExists bool Issuance fat.Issuance @@ -212,14 +212,18 @@ func validateIssueFlags(cmd *cobra.Command, args []string) error { if !force { vrbLog.Println("Checking chain existence...") eb := factom.EBlock{ChainID: paramsToken.ChainID} - if err := eb.GetChainHead(context.Background(), FactomClient); err != nil { - rpcErr, _ := err.(jsonrpc2.Error) - if rpcErr != missingChainHeadErr && - rpcErr != newChainInProcessListErr { - // If err was anything other than the missingChainHeadErr... + inProcessList, err := eb.GetChainHead(context.Background(), FactomClient) + if err != nil { + if err, ok := err.(jsonrpc2.Error); ok { + if err != (jsonrpc2.Error{ + Code: -32009, Message: "Missing Chain Head"}) { + errLog.Fatal(err) + } + } else { errLog.Fatal(err) } - } else { + } + if inProcessList || eb.KeyMR != nil { chainCost = 0 chainExists = true vrbLog.Printf("Chain already exists.") @@ -353,6 +357,7 @@ func issueToken(_ *cobra.Command, _ []string) { fmt.Println("Token Initialization Entry Submitted") fmt.Println("Entry Hash: ", Issuance.Entry.Hash) fmt.Println("Factom Tx ID:", txID) + fmt.Println() return } diff --git a/go.mod b/go.mod index 0d4a491..b940379 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/jsonrpc2/v12 v12.0.2-0.20191015223217-9181d6ac9347 github.com/AdamSLevy/sqlitechangeset v0.0.0-20190925183646-3ddb70fb709d - github.com/Factom-Asset-Tokens/factom v0.0.0-20191021172540-f9e30125a179 + github.com/Factom-Asset-Tokens/factom v0.0.0-20191025014600-db7c22b6c31a github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 diff --git a/go.sum b/go.sum index 2193dab..18b7073 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 h1:rfy2fwMrOZPqQgjsH7VCreGMSPzcvqQrfjeSs8nf+sY= github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885/go.mod h1:RVXsRSp6VzXw5l1uiGazuf3qo23Qk0h1HzMcQk+X4LE= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191021172540-f9e30125a179 h1:zpO4OYqH16iAngk4YIrC3FOJFyUWDgODp2NcdwlrUJM= -github.com/Factom-Asset-Tokens/factom v0.0.0-20191021172540-f9e30125a179/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191025014600-db7c22b6c31a h1:jDjY4eIs6/A+rR5Mr28dbPXs0xmT+FrB9pze7PYwgkA= +github.com/Factom-Asset-Tokens/factom v0.0.0-20191025014600-db7c22b6c31a/go.mod h1:HfWlWphJ30PlWXD9FTiAboLc58dQU/2A9HxBGnKyDpg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= From 1d6497c8a0d3d37eb201125313d55f6ae2d3ca63 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 25 Oct 2019 16:24:58 -0800 Subject: [PATCH 115/124] fix(engine): Revert in memory state after cleaning up pending transactions --- internal/engine/process.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/engine/process.go b/internal/engine/process.go index ead688f..26c1b8b 100644 --- a/internal/engine/process.go +++ b/internal/engine/process.go @@ -245,6 +245,7 @@ func (chain *Chain) revertPending() error { chain.Pending.Entries = nil // Always clean up our session and snapshots. chain.Pending.OfficialSnapshot = nil + chain.Chain = chain.Pending.OfficialChain chain.Pending.OfficialChain = db.Chain{} chain.Pending.Session.Delete() From 52e4ee1bd49b3b98fee52495a1a8164a3660f58d Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 25 Oct 2019 16:25:40 -0800 Subject: [PATCH 116/124] chore(.gitignore): Remove things that should be in individual's global .gitignore --- .gitignore | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 542dd32..954e521 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ fatd* -.idea -coverage.out fat-cli* -todo -.vscode -*.sqlite3 \ No newline at end of file +*.sqlite3 From 0ae1e59b8770ba6797af4729258b0a37e1d8b785 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 25 Oct 2019 16:26:05 -0800 Subject: [PATCH 117/124] style(cli): Improve CLI output formatting --- cli/transact.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/transact.go b/cli/transact.go index dcd0528..2c1bca4 100644 --- a/cli/transact.go +++ b/cli/transact.go @@ -366,8 +366,11 @@ func validateTransactFlags(cmd *cobra.Command, args []string) error { if err != nil { errLog.Fatal(err) } - fmt.Printf("%v Transaction Entry Created: %v\n", cmdType, entry.Hash) - fmt.Printf("Chain ID: %v\n", entry.ChainID) + fmt.Println() + fmt.Printf("%v Transaction Entry Created\n", cmdType) + fmt.Printf("Chain ID: %v\n", entry.ChainID) + fmt.Printf("Entry Hash: %v\n", entry.Hash) fmt.Printf("Factom Tx ID: %v\n", txID) + fmt.Println() return nil } From 8c5275127b9fb46e4ebba9415a9f90631f64b666 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 25 Oct 2019 16:42:11 -0800 Subject: [PATCH 118/124] feat(fat): Enforce Symbol length limit of 4 --- fat/issuance.go | 4 ++++ fat/issuance_test.go | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/fat/issuance.go b/fat/issuance.go index d85925f..3904322 100644 --- a/fat/issuance.go +++ b/fat/issuance.go @@ -61,6 +61,10 @@ func NewIssuance(e factom.Entry, idKey *factom.Bytes32) (Issuance, error) { return i, fmt.Errorf(`invalid "supply": must be positive or -1`) } + if len(i.Symbol) > 4 { + return i, fmt.Errorf(`invalid "symbol": exceeds 4 characters`) + } + switch i.Type { case TypeFAT0: if i.Precision != 0 && i.Precision > MaxPrecision { diff --git a/fat/issuance_test.go b/fat/issuance_test.go index 8a8186d..51107e0 100644 --- a/fat/issuance_test.go +++ b/fat/issuance_test.go @@ -181,6 +181,24 @@ var issuanceTests = []issuanceTest{{ } return e }(), +}, { + Name: "invalid (symbol)", + Error: `invalid "symbol": exceeds 4 characters`, + Entry: func() factom.Entry { + e, err := Issuance{ + Type: TypeFAT0, + Supply: 100000, + Precision: 3, + Symbol: "testasdfdfsa", + Metadata: json.RawMessage(`"memo"`), + + Entry: factom.Entry{ChainID: &chainID}, + }.Sign(issuerSecret) + if err != nil { + panic(err) + } + return e + }(), }, { Name: "invalid (supply=0)", Error: `invalid "supply": must be positive or -1`, From 6dbe4012f49b9f681b647cf549988b80c61e8726 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Fri, 25 Oct 2019 16:42:45 -0800 Subject: [PATCH 119/124] docs(ISSUING): Update fat-cli command syntax --- docs/ISSUING.md | 174 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 117 insertions(+), 57 deletions(-) diff --git a/docs/ISSUING.md b/docs/ISSUING.md index 76b28d8..f9d3fa7 100644 --- a/docs/ISSUING.md +++ b/docs/ISSUING.md @@ -24,7 +24,7 @@ Level 4: Root Chain : Management Chain : -While creating the Identity you would have created an EC Address, save the public and private key. You see the private key once you export the address. +While creating the Identity you would have created an EC Address, save the public and private key. You see the private key once you export the address. EC Address : EC Address PK : @@ -36,85 +36,145 @@ Now that you have all this done, you're ready to initialize your token. ### Initialize a token Run the following command: -```bash -fat-cli -identity -tokenid issue -ecpub -name -sk1 -supply -symbol -type <"FAT-0" | "FAT-1> ``` - -The token symbol can be up to 4 letters. -Once this transaction completes - instantaneously on a private chain, about 10 minutes on public testnet, you need to run the same command again. - -The first time you run it is to create the chain for the new token, you should see an output like this: -``` -Created Token Chain -Token Chain ID: d769a40522998f10ed82e8cd96f875d3163742efa07d973d799a507246210cb7 -First Entry Hash: 5b11d18d1d17ebdeffc2f96a50dc1e9e24c171df6f9e7aaca46186ff40fdad48 -Factom TxID: ac42be15931ed95b288e13696a0224c4d172fed737b84d6e6398475cea5c74f1 -You must wait until the Token Chain is created before issuing the token. -This can take up to 10 minutes. -``` - -The second time is to create the issuance entry, this will actually issue the tokens. You should see an output like this: -``` -Created Issuance Entry -Token Chain ID: d769a40522998f10ed82e8cd96f875d3163742efa07d973d799a507246210cb7 -Issuance Entry Hash: 44e6994c12315244d2920902273bbbfb000eed7a64aa55da30902c280131998f -Factom TxID: f93fe0e6056f9ca0b5eabceb9af3880a93b6861ddb666a0b3cec21a9d54fff8e +$ fat-cli issue -v --sk1 \ + --ecadr EC3emTZegtoGuPz3MRA4uC8SU6Up52abqQUEKqW44TppGGAc4Vrq \ + --tokenid "SPARTA" \ + --identity 8888888de45074fb3505cfdc942f80f4c9ef1ddd5c4633cd21a940288ffc89f3 \ + --type FAT-0 \ + --symbol "SPT" \ + --precision 10 \ + --supply 3000000000000 \ + --metadata '{"fight":true}' +Fetching secret address... EC3emTZegtoGuPz3MRA4uC8SU6Up52abqQUEKqW44TppGGAc4Vrq +Token Chain ID: 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c +Preparing Chain Creation Entry... +Preparing and signing Token Initialization Entry... +Checking chain existence... +Checking token chain status... +Fetching Identity Chain... +Verifying SK1 Key... +Checking EC balance... +New chain creation cost: 11 EC +Token Initialization Entry cost: 1 EC + +Submitting the Chain Creation Entry to the Factom blockchain... +Chain Creation Entry Submitted +Chain ID: 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c +Entry Hash: ca6a22133e5ad6c96afd26567b52cc8437f7507f11833b5607cf532487fa6376 +Factom Tx ID: eb9db70e8e854ff6ad7abc51d42a589c5c922345caf96508c0e2f80dd44f6e02 + +Submitting the Token Initialization Entry to the Factom blockchain... +Token Initialization Entry Submitted +Entry Hash: 271ade6467d44937406fe934e7d68cc44a18b61778cdd4a478b4353db4caaa5c +Factom Tx ID: 6df52ff302c6a03734ac94c0cacacabcbc9461ade1ff016e9d9e5866a11d0abb ``` -You can find these in your factomd control panel (port 8090) +The token symbol can be up to 4 letters. Once this transaction completes - +instantaneously on a private chain, about 10 minutes on public testnet, you +need to run the same command again. -### Distribute tokens +The command will create the chain if it does not exist, and submit the issuance +entry. See `fat-cli help issue` for more details. -Now that the token have been initialized, you actually need to distribute the tokens to Factom addresses. A `coinbase` transaction will be used to do so. You don't need to distribute the entire token supply in one coinbase transaction, you can submit multiple transactions over time to distribute your token supply. +After about 10 minutes, the chain will be tracked by `fatd`. Information about +the chain can then be queried. -The coinbase commmand looks like this: -```bash -fat-cli -chainid -sk1 -coinbase -output ... -ecpub +``` +$ fat-cli get chains 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c + +Chain ID: 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c +Issuer Identity: 8888888de45074fb3505cfdc942f80f4c9ef1ddd5c4633cd21a940288ffc89f3 +Issuance Entry: 7f8ab15b40aaece738d6afa1053ba070a0daa28052be28e777a2ee6258bec569 +Token ID: SPARTA +Type: FAT-0 +Symbol: "SPT" +Precision: 10 +Supply: 3000000000000 +Circulating Supply: 0 +Burned: 0 +Number of Transactions: 0 +Issuance Timestamp: 2019-10-24 17:00:00 -0800 AKDT ``` -There can be many outputs. You may want to supply tokens to multiple accounts in the coinbase command, just remember the sum of the output amounts should equal `AMOUNT TO DISTRIBUTE`. In the case of FAT-1 tokens the inputs and outputs must contain the same set of NF token IDs. +### Distribute tokens -An entry credit address needs to be provided so that fatd can pay for the transaction. +Now that the token has been initialized, you need to initially distribute some +tokens to Factom addresses using a `coinbase` transaction. You don't need to +distribute the entire token supply in one coinbase transaction, you can submit +multiple coinbase transactions over time to distribute your token supply. -Using the FA Addresses you've created, run this: +Coinbase transactions are signed with the same SK1 key that the Issuance entry +uses. The following command distributes the entire supply of the newly created +token to two addresses. ``` -fat-cli -chainid d769a40522998f10ed82e8cd96f875d3163742efa07d973d799a507246210cb7 transact -sk1 -coinbase 1000 -output FA2jK2HcLnRdS94dEcU27rF3meoJfpUcZPSinpb7AwQvPRY6RL1Q:500 -output FA3TMQHrCrmLa4F9t442U3Ab3R9sM1gThYMDoygPEVtxrbHtFRtg:500 +$ fat-cli transact fat0 \ + --chainid 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c \ + --sk1 \ + --output FA2kEkNgQ5RMNx5Y14HRQa4X8czeZqg74AJykR8f3jx4Cbk26gcM:1500000000000 \ + --output FA2mnS2QfXNQjdq6jJKxUxDnwPXzLpxivYDrYMtLgmRDbrxZztY5:1500000000000 \ + --ecadr EC3emTZegtoGuPz3MRA4uC8SU6Up52abqQUEKqW44TppGGAc4Vrq + +FAT-0 Transaction Entry Created +Entry Hash: dd2afeeb99232893caa52264cc80f094f78ebfed47766fd77ea99e8987e54a0f +Chain ID: 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c +Factom Tx ID: 693060a967a1ceee0353e35ebf66dd30f0ec4fd76a65eac228a5f2a10aeb448b ``` -You may see an error like this: `jsonrpc2.Error{Code:-32806, Message:"No Entry Credits", Data:"not configured with entry credits"}` - -If you do, simply add the `-ecpub` flag to the command and it should work as expected. - -Great! Now that we have 2 FA addresses funded with your token, you're free to transact between other addresses. +See `fat-cli help transact` for more details. ### Send tokens -```bash -fat-cli -chainid transact -input -output -ecpub +``` +$ fat-cli transact fat0 \ + --chainid 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c \ + --input FA2kEkNgQ5RMNx5Y14HRQa4X8czeZqg74AJykR8f3jx4Cbk26gcM:5 + --output FA2mnS2QfXNQjdq6jJKxUxDnwPXzLpxivYDrYMtLgmRDbrxZztY5:5 \ + --ecadr EC3emTZegtoGuPz3MRA4uC8SU6Up52abqQUEKqW44TppGGAc4Vrq + +FAT-0 Transaction Entry Created +Chain ID: 56bba15293b1f24849a7c3205a30db5981022776bea711d217437a642e9e080c +Entry Hash: 5e170b84c076b71363a13075b40765617378db4960a7efc851658c97291010e4 +Factom Tx ID: 404f7a19c515f42d057cc0f8981651750069e4c612cb5a3963ef5a5a18c94844 ``` -An entry credit address needs to be provided so that fatd can pay for the transaction. +A normal transaction can have multiple inputs and multiple outputs. The private +key for any public address used as an `--input` will be fetched from +factom-walletd. -The `input` and `output` flags are used to indicate how the transaction is funded and who the funds should be dispersed to. +The sum of the inputs must equal the sum of the outputs. ## Potential Errors -* No entry credits/ not configured with entry credits. -This error is caused with fat-cli doesn't know how a transaction should be funded. There are two ways to solve this.
    -1. Pass `-ecpub` with a funded EC address to all fat-cli commands that submit a transaction.
    -OR
    -2. Run/restart fat-cli with the flag `-ecpub `
    -This will ensure that fat-cli knows how to pay for the transactions. +#### No entry credits/ not configured with entry credits. -* Unable to retrieve account balance -If you want to find out how many tokens your wallet has, you would run the following command:
    -`fat-cli -chainid balance ` and you might run into the following error (most likely if you are on a private chain) -
    -`jsonrpc2.Error{Code:-32800, Message:"Token Not Found", Data:"token may be invalid, or not yet issued or tracked"}` +This error is caused with fat-cli doesn't know how a transaction should be +funded. To solve this, pass `-ecpub` with a funded EC address to all fat-cli +commands that submit a transaction. -This happens when fatd starts scanning at a block height *much* higher than where your private chain is most likely at. To solve this, you want to restart fatd with the flag `-startscanheight 0`. fatd will scan through all the blocks until your private chain's max block height and look for all valid FAT transactions. Now you will be able to run the command with no error and see your token wallet balance. A good indicator of this being the case is your `fatd.db` folder will be empty. It *should* have a folder with the same name as you . - -*fatd will create fatd.db in the folder from which you run fatd. It is advised to run fatd from the same location each time you do it until a better implementation of this is released.* - -*Do not use `-startscanheight 0` the next time you run fatd. You could, but you'd just be wasting time. The next time it runs it'll run from the last blockheight so there's no need to rescan.* +#### Unable to retrieve account balance +If you want to find out how many tokens your wallet has, you would run the +following command: +``` +fat-cli get balance --chainid +``` +and you might run into the following error (most likely if you are on a private +chain) - `jsonrpc2.Error{Code:-32800, Message:"Token Not Found", Data:"token +may be invalid, or not yet issued or tracked"}` + +This happens when fatd starts scanning at a block height *much* higher than +where your private chain is most likely at. To solve this, you want to restart +fatd with the flag `-startscanheight 0`. fatd will scan through all the blocks +until your private chain's max block height and look for all valid FAT +transactions. Now you will be able to run the command with no error and see +your token wallet balance. A good indicator of this being the case is your +`fatd.db` folder will be empty. It *should* have a folder with the same name as +your . + +*By default, fatd will create its databases in your home folder: `~/.fatd/`* + +*Do not use `-startscanheight 0` the next time you run fatd. You could, but +you'd just be wasting time. The next time it runs it'll run from the last +blockheight so there's no need to rescan.* From ec1eff6a953567e71fe689416c39e7f89874a076 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 28 Oct 2019 12:57:51 -0800 Subject: [PATCH 120/124] feat(systemd): Add service and sysusers files --- fatd.service | 15 +++++++++++++++ sysusers-fatd.conf | 1 + 2 files changed, 16 insertions(+) create mode 100644 fatd.service create mode 100644 sysusers-fatd.conf diff --git a/fatd.service b/fatd.service new file mode 100644 index 0000000..bac07c7 --- /dev/null +++ b/fatd.service @@ -0,0 +1,15 @@ +[Unit] +Description=Run the Factom Asset Tokens Daemon +Wants=network-online.target +After=network-online.target + +[Service] +User=fatd +Group=fatd + +Environment=FATD_DB_PATH=/var/lib/fatd +EnvironmentFile=-/etc/default/fatd +ExecStart=/usr/bin/fatd $FATD_START_OPTS + +[Install] +WantedBy=default.target diff --git a/sysusers-fatd.conf b/sysusers-fatd.conf new file mode 100644 index 0000000..a96f24c --- /dev/null +++ b/sysusers-fatd.conf @@ -0,0 +1 @@ +u fatd - - /var/lib/fatd From 0d7fd08191c0e2c1cf4060f42ea47898a3e860a1 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 28 Oct 2019 13:25:20 -0800 Subject: [PATCH 121/124] fix(sysusers): Remove home directory for fatd user --- sysusers-fatd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sysusers-fatd.conf b/sysusers-fatd.conf index a96f24c..2625030 100644 --- a/sysusers-fatd.conf +++ b/sysusers-fatd.conf @@ -1 +1 @@ -u fatd - - /var/lib/fatd +u fatd - - - From d17c7ab7dd5abc645ae5b973711fea55515cff52 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 28 Oct 2019 13:28:29 -0800 Subject: [PATCH 122/124] refactor(engine): Improve chain sync INFO output --- internal/engine/chain.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/engine/chain.go b/internal/engine/chain.go index b6eadf5..fbe2b44 100644 --- a/internal/engine/chain.go +++ b/internal/engine/chain.go @@ -94,7 +94,7 @@ func OpenNewByChainID(ctx context.Context, c *factom.Client, chainID *factom.Bytes32) (chain Chain, err error) { log := _log.New("chain", chainID) - log.Infof("Syncing new...") + log.Infof("Syncing new chain...") eblocks, err := factom.EBlock{ChainID: chainID}.GetPrevAll(ctx, c) if err != nil { @@ -144,7 +144,7 @@ func OpenNewByChainID(ctx context.Context, } func (chain *Chain) Sync(ctx context.Context, c *factom.Client) error { - chain.Log.Infof("Syncing...") + chain.Log.Infof("Syncing chain...") eblocks, err := factom.EBlock{ChainID: chain.ID}. GetPrevUpTo(ctx, c, *chain.Head.KeyMR) if err != nil { @@ -170,7 +170,7 @@ func (chain *Chain) SyncEBlocks( return err } } - chain.Log.Infof("Synced.") + chain.Log.Infof("Chain synced.") return nil } From 7c4bb3abda78fb547822f107bec19ae43c219403 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 28 Oct 2019 13:55:18 -0800 Subject: [PATCH 123/124] feat(systemd): Allow networkid to be specified with systemd start fatd@mainnet --- fatd.service => fatd@.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename fatd.service => fatd@.service (65%) diff --git a/fatd.service b/fatd@.service similarity index 65% rename from fatd.service rename to fatd@.service index bac07c7..cce99e7 100644 --- a/fatd.service +++ b/fatd@.service @@ -1,5 +1,5 @@ [Unit] -Description=Run the Factom Asset Tokens Daemon +Description=Run the Factom Asset Tokens Daemon on %i Wants=network-online.target After=network-online.target @@ -9,7 +9,7 @@ Group=fatd Environment=FATD_DB_PATH=/var/lib/fatd EnvironmentFile=-/etc/default/fatd -ExecStart=/usr/bin/fatd $FATD_START_OPTS +ExecStart=/usr/bin/fatd $FATD_START_OPTS -networkid=%i [Install] WantedBy=default.target From 2e4a483521a6992d260fe819ba22c5e8b7477554 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Mon, 28 Oct 2019 14:10:15 -0800 Subject: [PATCH 124/124] feat(systemd): Allow separate EnvironmentFiles for different networks --- fatd@.service | 1 + 1 file changed, 1 insertion(+) diff --git a/fatd@.service b/fatd@.service index cce99e7..d2a0d96 100644 --- a/fatd@.service +++ b/fatd@.service @@ -9,6 +9,7 @@ Group=fatd Environment=FATD_DB_PATH=/var/lib/fatd EnvironmentFile=-/etc/default/fatd +EnvironmentFile=-/etc/default/fatd@%i ExecStart=/usr/bin/fatd $FATD_START_OPTS -networkid=%i [Install]

  • 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 literal 0 HcmV?d00001 diff --git a/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal b/db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal new file mode 100644 index 0000000000000000000000000000000000000000..c2a58dec3d0cd0a7bb3eb04dd9e714037f715c3e GIT binary patch literal 1256632 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

    Ws?H{uxuF<21hiq9HYUn9Dz`fFR*T< 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

    Yb`XS|Bj5vDZQB6QyG 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�(}UE z>x1;(@!`JYo&@!@np$NoOe>}NX}(%+ zEi3dSkTphqufCLbsZZ5=>UDXJdO`g~J*pm%6V#pRCUvd)ow`t+txi?PL!W}dY9F?ujT$E4C6XauZaj5;zCzp{+$ywy|&;o#$KFc|+^x^)w4!91u4!91u4*V~0zj5K;(HMrk0E^DXYoady^oQn5Hk^2*1?v>5SBo(n=v#E(G2kbaSws7%q+e# zv$rww76M{FznA5c^rXn&g@YP;R`g2FVO5^j68(+5rHqz>;VkzN9;rFMeITBLhMA~n=`u| zL)#Er5nB+O5Gja_hz$sQb7t3LXdMFIoLPKxX7SA#8%iqF^JKKQHYU<5s2Z4WP}GX3^5cj1Th#f2r&>b0MQ@O578IV2a$wGMD#*n zsR5gSp&p2C2rPbK<1o|((HRkg=!EEq=zwUCXoqNvXoF~tXoZMIv_!N(G)F`snj)GY z8Y3DZ8X_7X>LcnQ>LMZ$wGk1BT8Ns68i?wMYKU+|RYVm;Wke-JMMMQec|BAp#Kr2!BLLL2p+*9SOkNh5flOQ z8Sx465%B@>9`O$G7V!r08u1G5@fEE5ymx$oJ*QL31o52)SmO?!6kl884nBb%1Q($9 zz(KjFxn0g{t~ZxUhs=5ARCA0u#7r{d%(iAzvyR-*tRi)jgUnE~gj~}s06hiLnS${F z#u9j7T!S70N2NE$USqSd2HyW?LEnH8#sDM1h=Di%hR`dZf)QjCHS)syz9vnV?m(Y_ zH~Me-9sLr#?H|&2>KmXhz>D_Iecb00`H^q!lnuKtVm1G+)o4OYmm@ zQhTJ`fYAYtL(BiIQhVt)ZLPLco1;zEMnS9pUUC+#v(_4F^dq!N(Bj`uI;Ht&*)&7r z)OYId@CJWHJ*^&5cdIGtDs>^$TwhXWNPW}^YO=Ib?FX&=JHnfLeYLt;P7RbbXy2b( z&7>;QIF*v3pyvLGa!a`&ZGboSgV45ry|P@Hr%Z)+^&v`<%*Yj#IHj%96x#GxQ9_jx zN&zK@k`7w)e~_Qc59Dj|S^22ESKchIkr&IeuA4k-h)tEa@*l8;ng z3WE0b^`z?7nREYK2V4hS2V4hS2maFzSf4h6A&J#PD4>H63h3ek;}%21L-hbq0`A8d z(9s74boD_2oqbS1cOMkc;RgkD`GIdJS(RHm)XlaUsjjzGIdzS#R;b_EYMHv+R@>BN zwrZo!w$%W2imm#nlWetD9b>E2>Ihr;tHW(ILmg(TawcX>XOvP zRu`4awz>el3vu1^(g$0eQzqEzS7;%KbI*#8ZFNTR?!jqknjQCx?EQ^XN@F|jq^jHM zgtXdLKTGv&byT5j^^N1Im6oZog97R{P+o z3YXg>y|UGA=?`1&lAG9SC$xmb<+e+^ZM9YQuCYb(9_42Ff<1SWRNqz`N~Z#t(Gd&ZM8)5&Rr}u zx8oMc#cZ`uF>SR#^d852<(VD#t?YGjo;1pio2&e8t2vT4zs;7sXMdLb-kv*C3bxfZ zP`8A8I$dgLt7)?LY)yqaD4aV*{>4_4<;k|1BzyD1*YY7dZUXdS#Wlt&rEE1$l590r zsbs4$a=5KVE8}f7LiyfS!=;+GN>(!3%A>5X)i6o4)lk`cltU!%Rcf&0&C`SAhxT#< z74KD`zp~Vh>nHWFRbSD29KEHUc3hI=-K#|9vmMt<@t%dA@>n}ALHf;BJ;Yya6)$;z ztvielhHi9|eza9r>8P#ZWN)77qGYh+I!j((Vx^~cT#V$+gPo*Hc3em0gsnPA-m6c0 zrN155PFiBC)}r?ewvvn5anVY8TeVcS*{X%I##YTm@9{<{h3vRyP{)bK(Nwx;t0u}g zTQ!EB$vC%>lHFDfrJrrpKz?Vd`jYqT*ORx}adjo{S&fu^?YKIUcgqn{4m+-va?4gV zRUcc`kjvYuy5e1~8uXgReF<06*{Z6N&sJ6BKDMe1eX()5ic&>eRgiPps;ufwb!DWx zc3i0F%{`?R@AV}_3AN`2i#Kf*q@>uYl;pjt1j;AvxB%s~tx8IpY*hmKdgH$MshX{d zDl=_WMD+SsSn|4CNN!`#Ehu?YqOY_7o+K(-PZQp90lDQ%WzmRIN!CXSX3C2w^ABhx za1+`H95eTto6J?_0vKg*ER+W%nqAB`W@EFKS-~s?V+(!-?Eyq61xPjS!8ie@pdG+= zW1X?gm;>Voj)1WNdcep4&0$=CaHEV-0>%x@X6Ob>-u-Xu7og4mei$e4dwsD!6UG4W zkT?H!dQ+&^uLQOF#h|A_W?j)f!#n>&={tN2Q>V9<#^yFWp&Vai4VQOzRPHm$$R%=0>d?~1r&jWAv zvhqoJp*(;d`=_A}ey8#SywT5vp86x9?!5=}H)yWZRjNXp`w~iisBhPyw*9UAOuj2$ zhO68Wd5^pa`r|LMoOJ(O2V4hS2V4hS2V4hS2mU`fAkM5#K1T%~nwnXSC>SSXW;js{ zC(NvBeUu75UOlr4l;eO9XI3WjcuOGK563J3s~#$utBRgfa|dDI)1vAOlg3 z0vSLg^%kT*k?cs2C5dDvfh>W`E(BQ|m;D-~A1+%CWHFQq$f86tYe5!4nGa-PBI#xz z3*oXWKo&%~45Tm0Z6FKavI{`wM=8Ql`jDfn0V*F+!7w{7na%tRG7m1<0c38Jl|g=m zvLMJ@L{e)&=ET^lAah{sE0EcVWZQtuMkLb_WL8{uJ;*E=yBTC=l&e8z!Ue8?%!u*= z$P6e0K&B^>9RM;Nk<1;CCdT#wY2dPvAaz_I6G#nXhk#Uxq<#Xapxg*Hz}p3!_H+;l zg@BM)*u5a35Yb9E4?)5Q&_L2pK*AT)K~f(;!bjAtWvxAj52;(yA0&KB9VFuc3Exu( zNn2@%!Ubk9(u|=fmxCOF%WefZ7-QQq;*3FLZ@~T;1983vasZJu9OR7tC@X;MhY}8Q zMqeVSF(CU8$@B-=8)ZF^Nkp=cN@paZtPQdkF7OI|wHpThVKsF(gIuG+3lliS7vJtLit=|x350DLT(ceMVM|lC( zsz=tc&P-hr$rcA0iLw~TIw;G7tW6|S5M%_(jUa2G%n7n4k#ts&HHf6(vNHp&vXK6$ zVIbi$YbDU*AmKV|B~Z%*xX@bi1xUEmS_@PH2^U){wiHOX+JdA{gM`a1Na{W8Il12Q zoj{T6EguB4;d*PW4_C$+_665U2YA7SQHP`%c)?|^g2W3hGan>eaIN36tidZT^%A6g z#kH~kUU8{2An}R|eLut*aK(iT+Yl67a6#pT*?7HWZdm;YDqH`(J(|L{k<=G-Yu_Q%2okEy<|c5!(=35!kw1-Hf44h!n&|#0JC< zi1mndh_#6C5o-{u5vvd@5i1biA(kVSA(kSRAQmGQAr>MQAm$^!Ma)CYMa)6WM$AIY zM9e^ZgP4w(hM0<&f|!h$gqVo<8ZiMe9x)Cv7BL1f8Zinn5-|cX9FdIhAci4^B8DIa zBL*P`A_gG(Bl;owBKjbDBa#q_h+c@Ehy+9rL_DH9q8p+sA`a07(HRkoh(UBhbVPJO zv`4fI*8hc2t+MJ zO+*bubwo8pIHD?|3ZgQi5~3oa0-`*k9HJ~D3{eIViYSc;K?Ea$5Ty`-hya8?q9mdO zqBz12Q4CQOQ3O#KQ3z2G;fpAM$dB+r$Y|8F2}55pe-=9&rxwE8;BT4B|B67sM&VNyG`n&xqrQV~C@OpAbh7hY^PmKOzny z4j}d;_96Bn_8@j6cEN4kDjRUx3se|7>++hq!%ti71$afFj9dCat-4wmr2FSO;5y(s z;5y(s;5y(s;5zWX!hz(RoJ2*kk=$_K2_M!rN{lL8`t8Jr3tGItI6L_5fh{lRKWsW| zd)(3<6YKxZY)Bb>?ao z@7n`eGxt8(=-dUNXPND1{%F_bZLOG~blHCRhOhGCsv$OcHEhnbL2rAk8y?rc;rg5@ zty@0d%CBvI&oAXe{rWBY>>3hyWQFY zYFfJQnX32R?rY^q8Ta5#j*zcAZVFv=s?fEbU+rDIqX(Qej)Hv!GIDyU)2$6m=Bo{w zoO%B0L~*)Z!i-+C14myCPU$@@d-+>woMJgSC+l@5t=(catSV9Sz>FDT&nJ#p_+8a; zqgwbjNL{+}cFOAJize6bEDQeeb#z4eQ)#*Lxx(INy&{I(T6!bfio37%)y3N#*b+Ol zH+Spt;hk46|K4)bg7%(*%MxFNPXD-n{Oa&Bk8@Nw?%#1p`>>~z4lHk!w^8S7h34=NZO$JRL8 zKe|u5O_@*gS605=xiPNs`gteLR=oeHYK1U=cIDD#j|+9$FsWe6H(`b6iM5*%5KKqHCt4ODk<|SzfwV6F;TQFVDum zD7=fxH)!npHpazug^orPs`#cZHz)l7~nJ%4MP zJW9DoH8c5|xrP^ba{p28(c^Ml<~~;mJQ-bcUE8$W(WLa_-nE{hg}i!kV!-RLhIfZe zJ+{xsr+9gJdfQ{))h?dK9{;)7u&?mPJZ%$qvRPQ-%{kQ`{4nSCz||R^#EN%6Z>#(B ztR~G@ysP#kjg4935L*A8a)Hf1uMy*GY)NMo1Mp@o%DiI6LtlYu=2mMd_s?~}b-;DN zb-;DNb-;DNb-;DNb-;DNb-;DNb>Oc$P=Jl3(<)EptcB7lR%ES((kxxe&qm5=6#~IB zQd&hJa4goB3Y>C*8&8^M_s`uV7nTcL!hQsgVC2C|fAy%{NY??^0oMW70oMW70oMW7 z0oMW70oMW70oMW7f&Z2ch%9u9+mON`SFy;V&^d&ap(zGH!ZruIwfo-j1vU*Vx?C$Z zYpT;;ARbx_Y_(bp{I@(vcc)wjTnAhSTnAhSTnAhSTnAhSTnAhSTnAhS{(CrJ)$*s+ zxr($GNUM(#X)lmgR~yD^FOXJ`5UagFTKyuxJi#*1TP_f`tHo~5q2V*E@dZTTBV{HU z6ZA~#Jf(n?2toJHb-;DNb-;DNb>M%l1IdY^MA4iaIWjvJ9nma0%}&T^^(>@%Qc6H@ zP-v;((*7ZVR6e@aj)84qaKp&D{eqhZ1l4TVpj}W@ zXomrvTJ(%99aK4@f7gbgF{R@A1yya|H83Q$L!?hw%Yi=KyTtp11qAr{bZ^(sCoI^1 zaI3+7K0SIT{l$j6$2SUW5EE6mL0re4A#oA4`!(qo9^bcq)POn({krsN9^R{YWKx~j z?hV7+_v+VTKvMJI|JQ~G4=*90>!VCII-=r}G&?u{dDjE}tm~aSS1c&>x|{mo=izPY zRBe>{vEPD0&7Q4jv-9zS!dHilj<`5`RhElwq61UM>}Z!oSXp*@<71OLPV63^)VF#-kJ#FAQ8fmHR&Ug^M_`9K z&5{y3cWe?=x<-ShEyAmU=^=il{X+f9_yzd4^0QLJ-*e*p{Q~>~{Yv=-`31KcJUF>G zZ)cOQnCMy=Mx@yv`p=%Qz~G>eP=ClKN2b-gQSM3fp2ADa^99%R=o=W{pv5z_a>tvL zZ^*`ZPnJ-X_Em2czJAn zE%QYj+@5=EgQYL_-%Y=h=TcAQ$q_TZf%b1tSipeBLG7w1RO{0rzJAa8RlCGiDHYSF zM(>~oLERfiH4hBz(xrFPkTT&ts|6&~?cA|?V5DWNolU-+F#o@H%Kj4>@ZW72e1{Pq z$mI!rPph9g^|$z5+PXZY?fd)fV_53sIq6!b_kZmD?Q@SOhl6&v8MCq8rjH@B^GlgJ zOBJ##&OACed3*M$CEKmpeIYvM>g<)5ZH<}MF*#%2+S&g_eTT6mzWA=CIljQ(;Cpaz z@SpO1PW}h;<3ANTza((|?VA(aZbft~PP|!q;Pd@UOk7L*mo{=TG#! zSb5X04;!8M+CL#-UE|X+kCq%iSm57Xp#C+|y9YkR0iR)%B9jM4*WC1bnga{`Y1@NB z0|H9@Dci4no+B@h8h?n;u&- zrSl(yefws5Uo=&^S?=cH4WH_~uD#wE=#$hxp_5M-TzDH75AgBxN$j1F(5*k*T>ZU& zgFOli2O+^>dTK@2T%Re;5dzXY%(4`p)T3*scuIx+Es{?$nFQJ7&xn z@y?4K+ZJyq(y_(n)2SnTDo=h>pJ9u4<}hda@F!1I<1RFpPWf41sCf2+NK zXkMhu59Vt#6~0w?*Srov_s?~}b-;DNb-;DNb-;DNb-;DNb-;DNb-;DNb>LsY0iLC4 zPPC@1U`-jGOgS=TIaY>m^stf)hay>r<1H6p@1APSM+}`w$_0#1l<~>zZca7xn>EcX z=9Pbi19$h)b-;DNb-;DNb-;DNb-;DNb-;DNb-;DNb>PoBU^V_vtC65+EtFQ9Ho;mb zt#(wrwNP4(en?+~v|6Z;z6NQvryzX|(rk@DQC8W2Q!cQ-*rdIWCif{~^&{ZS&XjrC zOfe^daQ|EfTnAhSTnAhSTnAhSTnAhSTnAhSTnAhSTnGMc2MTadCJ?6$)BVdaZLiLr z7$d)#EzgYnrq7dJrxM;i8L)KPgPJ2s6kuVgQjgX}3uB+W-hGCd6ck&1=C&-?r*FAB zaKz<$uZv%gwxQk>wxQk>wxQk>wxQk>wxQk z>wxQk>%f1l1A@xYv?MZu0+dBgkgc%52$B^RI6)+2d4Z#4i5GY)$TK`=1v!DWf*iv$ zR*)5FE66f%2%2>m-g1G6eV673?Ynux>PMiOYbf)%`PRH`J~Gcj%YmKdar2&EC1NTwp(CzJf6aZ?r*scxDL1uxDL1uxDL1uxDL1uxDL1uxDL1uxDNck9Uub< zs@9ZKh?a?#tSKv60}R^Z4qEarhaKctTDIB?WU{vHZ7to>7w39T`vqadE*ZEP7RRv z$oJHbY#*IgjtMDJk~Y=2CQ(W`?Y+1`PvKse)A?s|di9m&r{<6qCA0C_$jmiTbg>u5 z3v0{_azFmQ93?y#{||fb0d_SJ_4{U$NivhR>Am;fyY${WNbeo#ND&40i*%5tfK=%S zNN-Z47ZIfi0*W9aqJjkrQq;RS;N6Wg)!_`Qwyg1+9(K(m&Z z%=|1EYgLwgV%eNh&SD5b$zrCr2G~dBC*C_emvhW2_rzGwTJJoc^+lRm908Cb(6-17@{go`247>rb<;^X_80Wyx8=XU_GYTVOlW#YOXw zIno`>8@Q*e)_yAEW3Qh*&A1`6*-8EcYo7lZb`?-Viz`L71wn}4{6oC&cNa=Bf?UTM8<9Ss(VF-~{B z%Jb}dL6ulNtFf~&*5A7-Ps#=M``)hDBJXWp!>;e{v0rif7_T|2tQlSf+t79WH?~oc zm1P?gwU61mmcjHEY`rp#FWEZG_8MD@*%q@k$m(o0`u}9BkW1J~BxfryeK~svIfpG* z)b_Gvibf5#6xoC=LDpl774^$(ks|wsy^Y+(79y*#1&Z1XHeb=`&)!0RJ@%$L%rb1A zGWDu#F1jbNImnD`wn|RLW+~ISz}`SsVlx%B*VzoTyRzxXENq&hzK%^r{=%jxvM<ZU;UZlte=9<`eLprtPipR>#e9iWW6xA#(JW?i}gURVcn7QST{_+ z&AK8Fu`b9pth1s~hILZZe`g($AFvL{46MDP_A6_LT)^6@!x+ZeDASn3Uc_v@SZlO% zuvTcFXDt=kTGj&XVyromv1W=!3f2_udaMaD8*8k{`msiszK1nLR%H#;pG(K;qdA(@ zQ`Aecx{B;YR!1e*WVJDQ1*?U;%W5Lqvl@zeN>&}YpH)-T-e6VLW}R3Sg+i<{=3c`p zDMCMaMYP+n7m%45)3Zef7D~LSI3SjyXmLIv20m?D^p*=lA*f*iz#YLSfG;2Ghd+~ z^Dy;8<|4N;2e}189w9dy3n7q-)tIfacVU(?jdjdKrh>(jm7N`a%K-InnXbs%!3c?B zOQtDmE3|(w{ebp2`Y&pKA>YuRsokd3o+{HQsXamWX6-R@mG&pHr}juu->Ln9_FvlX zirN?2Z))=~+OG;Dw1>)TG}nGX*4KVUTG~&FdVlRlMeVru0R5e{`{@5e`$3V#wC^!} zq4pi7C)e&_dJgSdOrNLS#q_z_H|ozN*Y04pVcOToJlbvK5$!AFBJGx|?bH$~01H=h1ycJBRjU?Gr^kzxJ`p)>}J^=5_5OMfOZPgWRWmi0rSORx~zf zr;x68Qc>Tioxt>0wBzWXuN^}c(~e^LN$m)-fOZ)DceF#u_q7ku|F?D!*<3q-tflQ& zhtpo$huL1!-bd!w_A0W@+8#yiFKsv46SQ5(y4p_kf28eDgbQFhvc9%W?d}zAt1^vl z+7@I>ZL@MW)ZSC3_P(|W-Kn*Wih65p1KO>$chO#`tyg5NwRPzKTw9BLL0g0AL$%e& z2HGk`woqG%oUW}vdfGe44cc-=ZKbwM{rMW&Qe_&KwI#^8+G1p7ZIMbYp}nn4{eiX+ z`MI`0QTtw-k368grLv9G@c!ORo2R_SAZ;#ki#A7*UDjqJ8)>r?wNu&~YO7J&OodF^ z49tB`n~of;O~c%;X;YEqwJFF9+GI@cu1!ML(k7yRjP^RRhc-b`KcOWd-_YV}_uaJd zmZ3A_+&Bfj!dM0N)oX~!uVUJ%F^Dv;pmR6e)*;JGxUB<9jl{GABNTM!Wpv&ej+i@4 zL3?+odT{DHM18gztUjv@QVGVSfr#n@6!d*BVeTK`J`NilgZnsOX*c$}lo?$D}LW5Jp$b@vw`6cE7WNF|`w-QAb3^4hrl+d&Gfuh|zG{24|}_ z3fhGi)gKtpS{eG?R*0`#Di}RmAksHS=c;B3+SaCMz1T#7O>2xu)dcSUCk_R9Qs2GKjUMF^!c{-#KeZW#~VZK-?^jm{trix~PJ7 zuZYT+v9L0XyoHp}v!ME{QvhAF^DD5_`7rn0yees29`#u}w{q!^a$(AxoM=7GfykR( zfenUxFQ8UdM9wT~Gb6Jyj3Jp2yD}m=WKhrtr&nMb)2WQ5(_+&8H0bM;8j&xRN~(|& zjs0-9g|DzPx$+H8rVQgFcrM3ZAo@Ua0O-6}Yli71LF27n8?S!|x)Az*k=;P!PXX<3 znGQ7l`Wb3h16>+x82s=C?J2EIlg&V@d{Mu^!EKrM3~ZFSgKza>9rRCh^F4S^;g1k2 z@C9eFOJ-kVA)gvM6FXv*f!=~G#;Mra*wWaWvFWj-*yz~cSnpUTYmJpQ)+*M}*chu3 zs}L(5%NNTUOB3_?V*U3R=Pv|L%_qUb;5+k|;8yTi@JVnYI1ubG3I`j475r?lFqjog zvi9&2W|!c#V0h3!=w{pr+6GO7Izi>2G@lz3GIPVzX@($qApC#KMgAZD1OJYZ(f{1Y zWuEab`X5?L{KNWve~-V}nB}kWm-zGiX@1-vrL^-^1a@0 zub{I+uW_|RyG@&)!kBV0XLhQ#`Rp?`O|ph{OH`_S)A+6dE>lu z(mCL4H^w;Yon_9O&NOGdIh&_)MmR4y-S}ocj<0swIE}65+;M6;6`W$m11FD@$w}b| z`8RJYf0N(KTk;CLgyO6`D);i2^s92SSx&CD=E_AzJ$}5&8IEB8&bvzihM+smxc+BEk`x{fB+uyky_DubR8< zPweCTJ8P^l#NKCbwG4Z$y~Lht9k!>~WBE~gxZTh0VobDK+YO;_ryTEQ7qN5M8SG@X zWj(VV8Y`@O)|ci`Yp~Uew^V&zP)Bt;DX$r-_`pb|PN)tEk%t7glL{tW@xBT5YC^P3 zh}H>FDj_@)+>RGWxZ=YTDyeWn#Y-eqk`<{+2^FuGP)Q9Es&hgJ5{8}BCgI9V0z!g@ zBwV8tA{M6E@mdL2Jo=N>BUh(LRY@q51bmft30J&nr2gG>RKiy=A@Y%cQcRa{#d{=F zQuavwySD)e-_V4Jw@N5U!m#TmJh@50kwqsU9-Wi8pU9R}C!xwFL{g!IiocvtNiQT+ zJp5B|x#SOhmi=$OtWiEaq3S0@Qn7@J4~|syXX9lepPf+A*$zE$Lfa?Ol8Po&d|afO zB-FpfmMY=WNI;2~OSs~pPq$x8xROdoDt$u5Qzle+adJDna$#px6KV1As?+TZKr_Ki~en08WhF~xHx{PF1Gh%U(RYjC?? zB2#oSlWHbhNzv(wkBxlME|UH|s(%YAx~`I<%P=Xr!jf7O_Y+Q^qC zQmG>qRgI+NkuO=K!d4OX5=6d#+l{ahhg3I8bt3g|BN0`KBr{6okuoC1A{Eui_-hGY zJi4vK$3(tYA~iZvBO^5|Qc-&!9~Ajsid6qdMb#tTC-U`K(Gf)T zC?2&3@u(ifquLYyx2cP2PdsX(Q+3eTk(He->9jLM|CS6)vb6`qvHSG zH;P8b9M!UT*d{|OS|~~_7^%cf%8o~MFrGI`jcQ{&s)O;U=Ebu|`Jy@)kLqANs)O;U z4#uN87?0{;Jbkp6fA92By^KfoGM*yJ7uCjiR2$<_ZHz~?F&@>ycvJ`DQ5}p&^(`LN zw|G?F;!%BzZk}Tke?O{rQC*9w+NeaHsG^NXc!noLRPzQWJW;(HnD7iph^Qh)^)R}{ zMYS-hgi#%gs$h@Aexj-v)x@YGM)j~$B6Cy=qpH_7krY+I=$;l;z^ML3)vrlntEd`A zwJ@rLQ5}qKU{Sq`s$H$bexm9Z)xN0mMRhN_g++BRs$@|ei>g?3_ll}oRKLPo7q`QD z#_h10aXYMK+zu-lx5Jvn?XY5TJFHjS4l5J4!y3fxob+|L9o8gnhZTw2VLjq@)4t^{12fwjj$FFMKfnJ3Y zenB&ZpUcner!ju;W4`cVtOI%$e)aD2a^5%Im)>W*xOd+B&^yYjc>BGb-h14J{)OfI zE^qHG^yYZecuQ}B_p0}@dBYp%_4c}0f!Ee+?ls_=SJSKHm9aFhsF%;n&M!b;LrTx% z1w7OJ+kM1yy1%&JyI;FsxR=em?m71~-|rrA_qjWu-(iir%w6ElcBi^Y?ihEtJJ9Xz zc5&Of&G}Wgfm_q9${fokMr1h==|W^ac($QoO8};=ZLe<+2L$* z)<6%$0%x`})k$(*bzXJ`I(?k3e5cdSY2h?iwvo+beOW_Rl%-`6 znOA0$>17J(Lhr+0;t%nYxF^1X2#QPMtT+Mv4|~K`@vc}Y7K=AcUCb1d#5m}K7$W+K z?xF*KAXF4%JUCJ2~kkw5?MrAGn+^zq|oiB_HX>D{lLC!-?XpU7wwPiEtE<({YGF0B zYFU-7vQ{xGzm?OdNq;|hh80zN1tl8Y+$mTBeq%u9L`?*3&_qsyNy{3@E#X+N7 z?q|x%;$Bfm?_O3Y0v%d_{O%=%H10)()uBL#KGVBG)DNJ) z3;pyk+~vNg(8+}kcNkK0o0vtE$mxEqkjlNS(9?xZZ>E=WE5W&0q)oGMtVElp<7kdH zRrJQ**Qq9snP^iDH96~$HpR?}{wX?+O=*)I98=aNn>EotS;sL;ZIWbYPvV=2#&Fnv z9E_~EXu|*=@^vQ%(SDUkAsN~P3BASY05iEiDx`BCD5Q1oD-?2nP{{0luaMpSP9d9n zPa&E6twK@vu0n428->E|9ffhSEP}UJ-PhytC}XUHI~i+tMoYshZO&r0J=&a13mOgY{zs#|s)pe^uJcP8`AeAPl^chub5SHC&&rFigINF!9=g zxC&(rHAf+giV8ynR<0o?UUx$byz~dl$trEIwLQ_*4-qM%z48sR@sbbYQwB=>;{z=$ z>4Ew*m1Tf9j(|o1N?HvBK|p}ahp@54AUr@>{mlgk9Y#u+-cRCj^)rv6Wnj7Yb#^JM zuQgrmt1n*veVjSU*9XF~6d3NZH$ST^817Z*C0&G#ry1f0l-1K3qju7>pfY-ht_Y3= z)kBA252$Y(#zz1Qt7|uBwz9gJSZp}t2i1)iQqHbyw?Y?j62Wnw5Hp~x&T=sVI;@pt zysps6UW=fxkd88kvO4l32=1k5!yb5cFvy&3ooE{1~#H? zC4QB*7M`cJ44rN4XDVkKo&llb>3UHnDeFbv5TRoMwsvzXtF>7QVc;dx%Eba{Wv@{s z*J`jzYU#8>*hLV=I)xTCo~{=5B(&Iih34iv2!_pGGx4UfnwiTH+B}7(avQ>WiqNsD zHnCGF3jzWZ8jIlwjwca<36#}HzyKJ)#5+VIy@RqE%FPH1oiGTltOf$x#|B)Y#c-eX z6l(i+(%fa5NxkPP0JfT&rJ<6v>jhS8<;uaMwym?mEyDO`bgWt50g@2}!{=0Hk zbaVvAW3Fg4R@Muai7>GADmYlq70fTyA5f!%5M|?FD^}jjf)>Txl(TOstDM;c!LT&S z+IYxi?U$6ZtbqkqM*g6zGIl2f!;7OdKdJtJ8kvNf22j8%>EJP!v}&pxCAspI zaH=8f;|T3-h2m~5gf$UCfo{bFmS`~>zhg0mmsC*)o1voc+XuEQs#jMiBFiAmT?l=& zLSca?zOcC;E&ZTEAqU&`LM9$9#C<7eL3s&bV$nhflClc$2?!nQa(?-(vhu^vE(Mle zA)mljGoOuxlh3H5oOzuk2x}FB;k_%5Q&(AeOsuGRjP1(G?P>`A1Ywj?$Rz^=$BR9e z-bh(F<>v_VU4&jtA&1C>Fz^z~!TwZMcKHi}zk|@*D`d0qv}M!#DJ!eM4YJyc)JajJ z;qc2$v_jbV5U>R}GdsNzCf3l*#yDkV5VFEgcU!mBfQiWijQ1@KS}863xc8Q6Fs26{|czW4-T;x~fuO=Wo|UUQy-C(3n(D5q;; z3-00t=t#VIIc6G_SHqLaeD5q&*L6}B&WpRlm&TTAJt`AdA!|8>9@f$eLDrf+qIBOg?4j4O)O~z_tiSec}!+70z z6|T-C_>M!ZepWYlCTb3KxxQ81ssQ(#0#*(ygO$Q^EW>~6LZ8s-aTNwc7t)68h5G+om$+89k?g+%r4 zgs)=PVi#f`LeItfv2C$;V=H2B$L7SQ#^MmsFeKJD)-~1^`YP(hs>RAfG(-Ma_E`E@ z@|cXV;BoLv@Llj#a6PyfoC%Hw`-1JzAF(o66wD2#!F+|J;FVx#&=2C(+Xc;o`a$*J zg`i|m5TX|{1}OtKFjU`zf1hvkzwvMQm;I0Z6VU6h%YTm#gSdsI{#*V`f1>}I|FS>8 z?+K9$t@u;FkzdQN(8rM4_kGj*%lqAXfPD*ByidH7-Ur@pZ!`2MEb|t4 zv%JaPIB%pk2znAadoOxTygFVLuPpQ+#lI$cIQB!LEIhf4srXsT~&{PTi>njR&Yx|tU@j~6aUps1@Q{pdFK4)+=t$R zFPu-Ev(9n;5yUI(gjo%noYl?}h*+56ybiqu!y#Uwhtt7n2|Wchor*9sp^%dc;uKOj zp2Ouch*P*P??5lXrx2%bTpo}+c8)|QnaGNFjf zBeTdf5SL(yf5ao~8Mq~`iu2;MI4t%;zrcF&j#wyWiz#9}zXZJkgGC?FMYMsqgu2ip zP)-zsxP%`>Hjxf`1BB4{V*5|~XZxOg%f4!#w@=%LVdlmA_BQ)n=m~h6e{IjPr}7ZDeg$(|M*iUZa>kjL;SBieM0Qf1l|q9ohH=9&6C7TqS=vXK>0zo zxWrIO2n)(6 zTzdF^=3+piw!rOd&4ukWw;iT`=C(rq>{78_b(^C7)UAU2%`Jnx8a7|*TEH0bNkkQlA1&EQls!bvOIbh!rePv;MbEHB)o&M~2<0G@)F z^DN2_9VhsAnI*=cZSfb02KLb~Q{sM^$sP1V06lC!8RpY%Mw{z+SusO&N87O06Dj^5 z?(YN8)@}S=aN|9XHnaaG@`0+DAVL*(Ta0NuAG*VtAKE{XuA+a;xbkcN=y#D2%ty3b zqCr>JUk*kBLkxUT^oQZi+A}*p+RxYy{oT9s=z&xz9*I1d{>Yj_r0Fr*A69R)|1eGybBuu3e%7C1zMlkLbwBaX z$kwU2ew1g?{?RH#)Ty#OkcZHIVC5wmm5}%4GNK8;u%Ni^cSiFEhwAsyn${l3-bq5rP< zmdJBp_Pcbf--w3j{>D5&WTTOH1jcd06a#Ab?ikh4|FuX)v@9Y#0ji_DEvcs6hI=5| z`U}Xf#9Siuc`N@{j4J*uN#Euc?}`3f>?rc4q?&lss!X>2I`T`0@_%VUvB7@6GzKAW z$ks$V8&SvDRqYFBKiXfI)BwXw4YWUZb`xznPMFJr_H`$ug*8vRZtO(=HHWV0Yc^GZ zYXrN{{fWK;`LTG3XkH~Uy1SlrsQEn0Yoq@xqr>`04n_MTlP;cc znuK;n&~cpMW$`f2(6#p=L~p7;_n|Qvd0JAo)AlmVcAARxlqieIr!2a+!qft^Pdb-~ z{4$Zm+pR;QJyY}(U5qN|PD#bLllQ=4*hz=KgJ(x` zM-|MyU1lcQ^sTlVv(VmV|3$QR5H*WS?uQyK;*=FQA(T!-;BI;C+){4_;ueIpghSLYNHO>h1ud(P?=^EoO z+N)iI$mxn+tqh~~#cgKmV&L>%p97FFnFIu-L$LG98~7~WJhY-wNkIg?o&Kjp7>m%BTIGuH87lXU@}-KTq}tSnA$YqgawSS!9a>zdV_zs-q! zl+XNMtOoEDoyscUPV=|AUHsAhGQW@2#?9z%_NVj1_E@=y&-I7F^JiAR&oTVg{Aa(O zx6;kaZ~KM)JUqSguKd+)iP@ITBK_;OyF_wxpO zW4)19+!^Kdg;Ako{B!FSucg<>W99?9E6fBa?iKKInx(wdUf@46`|?laJMKgG2jjNM z-7D@n7|(s`?lVvD4(=NBH#IWPE816aWPX5|$L(b%cbma;b`5irTiPum4?5$l`Enrt zjt{Y>SWDq)`zz-U=O^bL|Jb?CTfy`83FiZ|o3qtiXzg)UI*Xk*tzVp(_FeIYGsGO} z^mBc)u=SGD!Mx=(mNlKKPI;$YosSl@i#n`|7@lcx5ek;Q}MAlDGpgu?Bxq#&cG_MM7+hhRYpt}}jG}@lDGG_)YP9~}v3mOf&t~7`C+!RN8T*)hz}{tVhNtFt?6+Zsgz5I{ zW=rEQdyw77_zqSk*loA48(O+u%dTvfwTr=z;~enRo67cK&cN5!KQK1`|IUAZTEAG| z!(5SD<~1nEkIdudL36jc#awT$Fc(43)eLhYToohD!De5x8}wYYG#kMve-*QwS==lD zeOH;x)MjAXrp6x|C-_7D1HZ#>@GJZrKh2NueS8Ps#Mkg;d;y=$r}88|h7achcrV_W zx8coredv#?2v=?qIIG!sdY*#2&?EPk@dxx#+%vv1t{Yo5%{Zai@SJ2d0B9RrSK&8r zH-HWCZYdN-Xxpu{sw+{)?r~Us0J{7Un}uJhlzy1D#SUATAWhDzxDnj2pqrEBF&o2H*m12h zoL!}lq7)oN&}k$?XYhMkWoNi;Rs4JvKe5|bXE=7EYq#i6V7IugttM*%;WNyS-Lbm1 zfc9rm()*b7A{l2XsWf2>InNM&Cu|`s#Lc?mXBYD@e3A5+Kr0JC+e_J?9fo7qc2HVc zvTz_s*AC#u-SM*xWssEN9{L)SuPFI2enV#_CN(h6YNk#Jhs!>t-_QTH?Xq%dZ@X!B^NjOHJ zv+QVK*SpU0Qa&32521J#Q;@ZWfL;B%c8$_flJhd*I_|53bpyUa9?DRVQlKv!j;tDf zwxpCe8m;lOl=T=kp(~k%=X?fj`M?#`AuT6uG@dY)@EYM&LK6aZ>+5VZS>fe5lB^Mg z@G8Xqe4XK7j?Qq%7-}kIfzAbW1rG>|VkI7CxOqQH!!Zn<^&u-P&R%5oB!tzbJ6YWb zT?t(Xoe|nevO1FW6ItyE?FelN;dMusKWk0SR)nxOn@!Ga zgshbF3R#&6nFtvP83^g+e!Mp53e$GU<50-d?ou#^&Qi%QF|T&Xkoe?JvSJ!c$uDiq5#M~w9nm&l=>TCE+*nEbiF3Ik_Zqf8fqW7H{6?W`tzgH zrS0|;_PLKe0ckkrTBplR+e2x)Jq*{-sm^HYy=rg_g(+F<^C)py4^>UwHQ!(n=zs?Au~y``tXy;Z@_!Tub$ zTcaF}p9Ar;GJe*=&*8qhQ-|$EVRQ|`&!#>Ul1>}H-pdl7FqLeCid9`Gxh<8 zrDsH4*8f{eq1&jFfwpw|GoQKa=_{oDS;LnPmErQ$HX7JzB z_USF)bEn=ss0}-6Mn{F8O(5s%dSld$@UvksLQAJNKwUq06N;%G>bm$@CrE-l)J9z^ z*ressF^om80Y{QTuMVG{UJX7ioqpv}>2<>V>Il>6CE(N2>EvZae=+=Q0kepU!3rB1 zE1;9d2Au^x>B5s=3_PA*G*&>%5>}1Eu@ta3T#Z042!8l6ihH96v&n$ZG zSVc(9g{e7XW8pY+pr+qD*-&SN_xQEbv&3G4RBBx5D#{4{>N*ba>*-?CAT=#~LMw(_ zrH(BDPb&C?yD557#MXnRb|D#T3qP#UAH=T0k@%?H*jeyXizj1GLDT&azO|N5r(bdO zOHs`jNTVC@nGAl1YH<0(hp9f)NKDi(@MX4VR+E`)4mxrb>tIx8|IMg$Rd{MH2664t zsI>d1`!md-zvW(a&%38#{`_8dtGnKP2j}hwHq8nb$09 z4l;Y2oy`}`CT1OTxw*icW$uBetViZg@a%Qd{LDOO<+Z9noONfbP4K<9C-@rX7F-HG z3XTQ)gB`&}?;Ol2SRBj?rUw&(F~KmHOVB-NAG8P>1U3G{2!5)-3%LJGjo|xt{4e}Z z{j)G@;D8#xhtd0G{(SF{{|3w#80(Ml2g1q&o&46`ESM)y+pi4$AVp#AfvkR7m>*z! z|9FqQpS*9qo8D(eb)&w~&1eTtV;7Ax#!+LRvEA5UtTYyxS)m6xnYY{&@cj2D{{?yi zzT(&UMSg}Kg=fF*d;?#}p?8B%gD1aN_)z0}-j8?V?Rax|@~bWv%Xz$joGvGrRpb~s zjOUR3Wp{6jY%g0_X=MXh!-~llWJzzbEGTnYO=L#s@mK({_J%oGJQKh165_tN1M~eq zg=qWZ;(*v`jue~3YI7Vsy}xPMFysGq^OSg13>PoK(|ZSRooFcSw-zm zFxS7aT^pX=%fMX!Ja!g44NQ0W$F||g{gL&P^(}<^-h|$za}W)9$l7CVfl#mI)&gsm zH_n<2p)pylk=CHtpDNbFJfxyM%%UpZ!|MItkN7axshAI=`M*&g-l>)~|4@!kU!DV75dyH=Ucz6|Uy|>HO^6b8b0TtIielS-eyH(an?<9Amqsu4qAV)l&q<92W{8sLp1&&GVF|Ij`Jnjm7Fce7o3&ITFzXg7p{y{L10HE zt6=R!e`5K+^5PNNxenVr#JPCtVz>+~m@FB2Jd@Uk*aN3_e>R8D2sS7?{E ze<5=0KxL)%qiC0s$BEGU1|6cX01=ucCGEDP2|uU6U9u;-OURr=>mE_ZVHs8&UIweO z6_@BPCa^P_d7KKNw}ZXV%##?Sz$!XzF<;<}A#ytSK&Okscj%ye>oTVMdP-znr!=yj zQxsX#!OnITlT;`%PJcIC#*Dorv3HmyGwI|fV|4O+$!~D`2J#DJ3Y!xR>{VvTMM1Qa z^RYyB4w+2i&>rh3_oH23;&=_KD(NIvkvLYsddQiWoL0EpN8d18$5fj!&VsAC9u1MtKm&>~cu%r(2l9e%4eXtoqMWWrjS{m7;j z`&bJr724rahOD_*fOd13Zly{uu?}4`L8a8p#8E#Mt^^8;P104<)SxQYL{RN$!cXC! zZ(`zZ8e1KR;c|A3tQgwi`em%4psLu=nu`90jOtzka~9eS3@Y9FW&yP8>vS>H<3FGc z%b_Cc+C7Q1avZB83ZY%cdPX$zA!`d90%EmIpKR72Sqpx5s8Xwy70sF^?Y5?I0qq(P zR;EfNTyTbG$+BJ##Ze$gk8phd3IuD%%Hr=xTFV-Vf|z?0r~~N7=B< zD%jcBUVatry~cIq9zj)m4{t)YLD%eVy8-TYH+{QZ;xQ)gGOH6|DLjo|U^~qZ(B26% zbu@kiqFG9yy+hxE+z!hFsXq^^$3prFwhewRC_Aw#>{eb3ZCH#F(&KE4nGJ1NloISA zY%^zQCzgqPPs~C4J@}EK_Vb>81-VI7AX-MpC zMPbbLu6dBCuSTvHbj_|e_mZts!>~@$NnXdRpnsil4Y}5!8Xm69%GSVdDRr1@%z{K% z=TI{ivDJ1Tv{&o*!JfueSyZJGt6;7)-@AP2e4k!jt!$U;UM)y;emimHw` zpV=3$vV7E_=7ktl<<4t0AQ~Sc^YDp8_A4^CLrp|(zJ_d_u9{qe{(LTe2mQHpYN2wP z(2)x*RL(5u&LOF}&%paXBSim+08v<8)uQ(+c^6f;U1~M(D(nzbfLOYE-Oyn9-e+8LZ z+#}l1V*`blIv<*;Osc^{B{fh(P1LjNZ0ZZNf9 zgZMsl57H+g2a0M$s}IrWf*fGdDIK7{8uD_sf)t^%Z4OeSr{lrDg*UzS3 zPT|tdtgpz7{=OD9Onn(0ULUI)`uphQ?`@4oySG7hFAMsz;qZGo=*UuML93uKtT|X!t`Ew~6+sDz+s_qb4$=g%Km^Qx;{OWI{onXs`k(pd{SW=4 z{(d>p-wDtDYyIW^LRgo7nm@sR)qhzYho}GEeivBBzB#NrSQDc6%fQorK0mvk!B6RX zvMs}YdeyuNUP-Tzm)pzYrG;pI>FMrM_cwXaec;}8Z^}i`dvd`&;~tZP-2?6} zceA?=dQjeW=epBj4Z_#l5$+(j55)Dib6dC#-CAyCw=6u_=67>I)I=(|J6s9iPv;ls zd*^G}9-{j%JD)hGoWt;ZyWQCcvHeS(`OYk7iW7HUafUfB$$3ssr<3!d(-fX>t2-}1 zgnwaJi7=~^4xVlu$AB3B-{p_;TUd?on!E^6{U6D~FuOmyEH6t!6n}E)buh(0GNTwI zj)@1NFU;jHFAl;i{>kD+F$U)OXM}!)HR5IQt%!-dV!4Pzgg{|2Pc##6Lx02P;xeoY zuuVJ`zl!Xlf!HXjKs3QQq1MTK{_lASJTHOgCGfli{?{l0H3-3Rc!T3A${d4^I1Z(7 z9Q@!oG{SLcgeSxNMe#E?e&P@p&xslrTjn@y!*SS*2k6I9P>us9JT-c7$cN)F3CBSZ zj>9k<2WB{qaq;w+vj~3TKn}-&7LEfd9LF|c5o2}yI3~z(9FONgPd@y_F*J??EgVNU zISvbP9CqRc=E31c2-9MMyg1lm;2?~F<8B5H!WcM2W6%h<{tyQXbs7uTX}pzZ421TYAyUChIc#&|AaF;+s z=>`p@8()+2Hi3rHjay{VP`W`w>9hiaPP2}6<8#V#op6nCmGBvXVjzsmWPM7wM7T(} zK%jwjTHZr9J|^c`0*$B}XUL*~b%O@h4d{JR-~A-v1c3(DjbmgTB^)6fCLAIhBpe{n zh`Oc%E=ZX|3Vyh~V5SVve( zSVLG%SVdS#pka2J0j(R$$w{N_2J{!HLt0E&M0lG3o#QHv#@CIv$a<47moSGwfi1=? zvfd!jAipt#tm%YlgsFrngvo?Sgo%XL2@?oOgg9Y5VH{yB;WfgmgfRqo5L4e_G+`8B zBw++$IAIuJD1o90jKO5Vv$WdFON9P}euTb+K7?L`o`fER?u2fHu7oax&V){cj)V?` z_JnqXwuCl>)`V7smV_3B=7eU1ri3Ph#)L+M288;AdW5=!I)vJUT7;T}8ieYEYJ{qU zDul{}N`#7p7YG#yX2<} ziL6J~EG_m&>^`hGcokL~JT42w_Q`Cqt+BP@WNb-nZfr_yY;1U}U#v^4b*!Om9;+^1 zlBHthVnyJ6bGcyjHd)LHp252Z?gd{4p9W{dpTXf^cd!Yb{};mQfv>}C{lP&mc=B%^ z)P;8qlnM$2+2DEK6K{$;uukBg{*N%ue;uCoPr@kwc32m1ng1p{<&TH){g?c1ej6Cu zujyA1>*2iudHhUbnV-TJ@MQm+_q}%u-X?I?JL>Hf-NcVDGk>+W$eZm=f;Ikz$~0ad zuM^B1sP9$v%6Nsuc`pad$`3s5K7r_myRequMTmX)z}@L?aNmIl`Rnd{F~XhUCW!;? zC|Jd>2Rykqb!)p7-4fD+5$P;28{ZMrTuror83Om6+s;+79iG^a!`gjYowYC%e=a<$ zk9CGiT~>DbIbEFAFcZJJQ_d*@^YAk`$zZjt# zD{`>xB|C@=A_c6Qr`eB04jADt1?%ZGff0|a_EP%`7@u!weFC$oe7lDA)GlsMw^mpS z?ZI|FSngsLOa$s}SFlc42kchx*2hlPE&GVQ7ozx+>>urKtPR$8_Gs%f7)sn?uT@vV z^MB7v;CTr=FMnOMG~-xA_-VVkpwKHNCK8pBms*ll7QtDNx*`N zBw$HJ60oQu30PT@1gtGd0v4+z0V{rzfHgl!z^b1lVBJp=u<|DfSmcugEb~bM7WyOs zOMQ}n#Xd>Ea-Sq%!A}yf_u;l7PiPQCP4XC<#~)lmsjZYIAt4vE?2E3gA&nfQ3Ox2=_1vSR9lD zEDuTo76>H)ON2@eKf_I9zq>nB6Wy($s_1SC)p~bbsMfe^L-oG9CRCl>w?p-cJ10~l z+*zSI>`n>QM)&nl6>}$qYQ8%@RBPRFq3Ytk7OF|^@K7~yhlT1Rw|}U1x;;bnhTA<< zh26%XD(E%}RVBA!sLr~zLRHnR9;ym%#ZcW7H$(L;tins>au?=9lKKYb`;ofi zGI8{`MVZj|mHazYw`8hN-GupqwC$HLfvp5*~-jj*zWs~!A=-U8uQE7kg%0;1C=N1mtYUj;RtrCgUm7;CvTLH19wCy`i zEL6+w#P?X{{1p0@%0x*n5tBmSV&~^jEfR_P_O?iz{e|*rn7Tle4%J&QwnTsJP0>75 z^JL;|&4n>2N}VGwgle{&9jaL}Q5R;y8=fiO3|OO@)O4q0sHO=Ks;N%ZP)(6FLp9l% z9;(-!_d+#6)D2aVlQL9sXML!~3p-TfWa3+n6^T~rHIb;(ugdSjd}EwMD=^ww9r{Lz zL7^IHC%(taVo2y4E)qvI%=stu4RsP{VTha>`UZ<1Lp8|09IAmL@z(~xI$cy6FNqVO z>MzcOs-H~MnZ8bn(AP&K%B8n>5c+zFL>=rYu7|!J&L^SjE)uOzH)nL{>nc`-s*{~K zgB@jo(AU999;)`v`=M&*YzkFdJMn$nIC(?gi!jDX-=np-8>&{$v{1E#mB%Tyg_Ay1 z&Be!|Y6fw-l-g7z&VCcQH}o|YiL=^B<_>)gMPkqOMTXE<&$%6{x^9k8)sdA#RohAA zs|Bl~(_gFUBnwpyCwr)>%Mqcf25YTTzAB`4tmRI}Qpoi}Rlr#gs{D4M{PKxJ z>E@N4!_+(?aV6#!%i-5w74-`+(J#<$=CB_RJG;!A21A}xjV;N!uoxcV z7#$3P_Xf0ucLvl9$_GVYrM`5559{=)XaBGLtFX4fQFyQ5d;Us)fjKB9+4O06K#Pk2=eFy9OU-C}FN_;!K_1;o=XW(Rb+8^Tef+MeQTd`UFFzv`|MP#(OW=74JTHOgCGfli{^yo}yWeBns6ME&f$EJa8K_>Uih}BiDmSPes0xGXjw&ap zZm3d$>WZous4l2dfa;7Y0M!YV3#uck44^uoN)4(#s;r>ep-Ks=Eh<=1c0n6dIY7OL zDi^5MsIq`+g(^L$mZ*w=YJn<0sOG5BfNF*+IjE+nJWx$gl>pTkRdG;_P~`>H5LF&f z4N&C+RUcJGQ1wvxpz5M32&xXM0-$OuWx;A`3u>W)m0cIqM5S|UK@I4}fmCZjb?CYH_7)w=AyjI zLFOcB!?(#s{z&V(l|9~7s@-L9kh^hj)2NGIQB^$t2*o>O$5TO}WW%m(CXh&6gZIIBADw!K3 zw4@-}=OCdeRTApld~8e20^o$U6r9FRkkFW_t=55r)>L`l013^h+G;yUXit?~2|K_B z)$9sRXi&kaEdVFBsAhj~LW`<${tXhER3(2032my97eGRzs-y-IT2+v`Iyca)DsN8M z7q+WrZ*W4ps?z6ygoYKQ_8FvO%W8H4C$y|;Begv=tx66832m#&`ZGvqT$Q{ETVd;J zsxt$vD>#k9Afb6xvLHxkUzMx`5*k>LdLEF_!Ya7~Bs8%~W&{asEJ&6XBs8)hHE4F` zLo2J~c#zP{f;7&8gmxCBt_lGfS|xu42~DlaP!%LJw#r))B(%05*+r1h+=A4e!jWTp zYxV?%?X6h~lA*m-+e6DZKWuQ-bwCZSJ`f}|xcUu{)ZpsNKthA7{+22lYH_vSK!z=@ zsshyFYL`G#iwkQ)*z=*qg$mmo6g0S?vOzMnxB4x$zCbngf1+QY)4;~9($zoFPR%1I zVXxHwpPfJOpU)nEUW5|=`OJa;-n@bT<5>g$&KdZR^9BCLXA96=fy7LK|N1${{f3+-m|~5uiKaGv-SyCvtW`6 zmk#$b;ZwpT!bQRb!g<0u!Y71}31m3?~dD3?&R93?>XB3?vL7yhP|v=tt;F=tJmD=tbyB=t1aC=tk&D=tAgB=tSs9 z=s;*sXh&#EXhV3B(3;SS(2~%C(45eW(3H@G(3sGO(2&r8P@hnbP?u1LP@7PTP?J!D zP@PbXP?b=HP?=DPP?7Kgp#q^ip&X$sp$wrkp%kGcp#-5gp%|ekp$MTcp%9@Up#UL2 zAs-jAsrztAq^olAr&DdAq62hAsHb? z2nasGBe(>IAPItC6D)#B-~@x96Bt25IR6m-Cj3QsMtDkiLU>I0lkkY}2jO?ZZ-ie7 z4+*~zekS}x_>u5{aG&r4;d{b&gnNW<33mzK5bhAZCfp``MYu({N%)d*gYX66bHa7P zHNsWGXM`(+%Y;t}mk1XL7YOGG=Lnw=J|>(cd_*`y_>gd#aEfq}aDs50aEx%2aD;G} zaER~$;UN4PQhftayui_<74CWC$T}4-V2S%$aN9rb)pqka4j9k>JuiXhCGflio|nM$ z5_nz$&r9I{Cnb=S(G*%EqYe`MA$@T4fGZhT#XMUXR%#NzLzDe`!-mqUs|5Itu;bm=Z54$>k(0hZjb*SIz9c%94 z$H&s9e)(LBD_5-{<@a6sy=&jc^?H>`mTucyX7yihOnY};*DTfAp6rx+#{b6NTR>TH zbc_C7UDa;5>)#lD#n^r?upLM+p_udoyvx`_rha8)Tz^a!1p5} zkFEB8zIEXF;#xQUnDEP^CvPrC|JAepo%}h!UbE}S6lrFHZ{_m$-X3TXh@JT0O{zj) zw%=A{$(d|7dZ*pLbayX)#*AAhR#?YKsCT%tMNWFXZo~7>U!BTL+C+cdXHMj}>jh)` z&rDhJPCV8zVp7wfb>cfLdCS^7RStdqb+PADMlW7fe&X0>S?WGpzUJ51bxoH{s~lKS z@Q2q?)k>a;&pMw3oM+iPcC?ly;Zbei3t-Fb9$&-JT+Hs7|WZ6NE4 zzAuW*`gm~Cx(Y=fr7CqYqWy@r#hy$(w6cDN`kilNTev%o^kbH_$&)siG=AsG)rAtj z8}mnxisK?vB@Und6+dIjt+Q5Gr^d9?@7uMDJ>Ku4k>%q=^~cwz7aprUVb+DTyJx@Y z*r~(2c&#&3M(d335Z__RTXL=+`5`j-ip8&o^!k{&-S&QCOMIBzZheZG-zA7WeMo&= z{$^Cwgz;J@;r(9A8x9^<>*vS}gWGrfFy+`6`kXu^lXj|nVQ|!dHrtY(Q?9LfyJu_H z2Ak%ex={N5!}6tyMbI_NS3JtrVOw~)@9yt8biRDUn_W(5iHlC?IDg_1{jb}V7JQlD zYPBD3_AH>@+a;{Rt&@{eshnl{l=ZnNRmU|dKWf>` z!HJAZCleLFn&Z{XXC-16%`aJa=<#^A&pnp}+h%;HEpL&#WO;riLFIHWuSd@c`*vFA z>Z@zLZPnmnx7`<6q3GhHyWWYa5}P2twz*?v;XVz+&7x5|qS_Rgs&3eJ=}wb-2coXA;@9~Z^OS@w;*C|l7CU`w@uDYoeAOW;@!YvZ zE@#RZBjv5vw8_ZqMM#Am`%cDV9X%$s6tqr!hb3>3s#b~VT6p2kR_VBdb=b(MW87kN6W%Es35S;y8BPJGvLx?Skii&H~h7pwQ%$QdUNgoWiUY0PSU zVpa9r@f`8LR?Ux<|H)5Ubv@mMZ+X6eqj}@5d?c_q}FJys`1ws}GSs-MAkOe{( z2w5Oxfsh4476@4&WP$$&Es&YkB=H536|qu$@l+;OiZ854#Y*u;*D}$XMtngazK$MW zD1={5@e782Twso#edozzd->ciA}(+a#}Pc_GY?+jLg+7Kfsh4476@4&WPy+cLKX;F zAY_4%1ws}GSs-MAkOlsmTR@|HFx-}%{AVNuV;OQS{~=i+=j4M!sKHY)ICRW8P{auZ z=NDL%xq6!ux$_P3^93^Ve1U5`TY%>b{5L=G(BXwF5VAnX0wD{8ED*9l$O0h?ge(xU zK*$0i3xq82Ut$5#fgj(fGRPN*Z*Udl3&c0Z2=WEu8)_qqe1Z5zgou2B_{K$WgQ>!3 z!MH&07v-1b-SpQpF~5MWjFY^+?qny4HQ&sv_fo77t}^A#*mB!9u8`67x+8Oh-k_1+Zg`{;nWxJIvyPlp{_ zQfo+0)p~X1;-x!_N?h$-Un-OIkNuCA=N&iYtNck@m7hFhlR14&U~b)nr_$COR&`=@ zM7>>?j!xTnXI8Vzxp!vmu20!CVA8Nbo_(y;58aZc-k){9Jv6LK&*=XB`i2!78dj-n zWRITpBkOi-Qmby)_Pq;rtyX76^t0(Vt9C1um1i1VZ+@jcB|E?ZozsrYYi&c zG+%)#_3E}M(4viy z)fz{|I|%t-w|e1-LXq6+Rd)|7=1c4Z-Gq3FMNCT z_eNWDW!!h}n|fQeb=;I-*NNlKKUw`h%{c#T_5ZZXIeb(e6|FwjqfymLKaO{B^S^HO zeE+J|J9jRfRqgZJvj;zoYE`3r{bwHsEgIJN>FQQ{9xcj#edM@mm*=cacDYql5`gU&Lut4F;bsIISP@XsSLg9tOi-Z>q&lk}m z{6Ff45#jm5Bg6BD7YHxdV)*cw{(N2rxH+YjqpBtv9q)MPfAxYz7A#PxNCbD2V>4^t zD)BgKU-o6*FIm_38W`ELZnLLW+4i@kEQPktf4od8bD&a-3Y*4$z4&3%-9&Dbm+TmU4Ce&U2;^1!y_bBYyne?eTF0A`c zH>Z3<8Wd<#DZ1i-c0KF#u2a5Cr*io_4yfF}K-~g88Z>Dd8QG;v|3-z1R_I+ZUv#a` z?JGsr6o!qvIeorh5&zaD`#*5Nf7&v)JD>64AM76AICbfN$L@v2?J0h{zuz^UN~bUC zH#)rkYyTggdObc`U~jAOTWfFoSZGcrJxOQ1RI;T>#}$m(m2yVjHtY9Zib}mMW!V)w zJI-t$lQ={5l>bBR&gYW&Y|U^7!GFo_b2B|y*z@mfzbuQ~^y}?Xty67& zpXz$1C1mA*bQP!UTKsza=7{P`+V!0=KT zZczU-+IxWi5C{JmM!jat@Te-={)~5Gfq&WT1&ZX$m;YZf`?WloPqb9G)g1fHwM7}S zx5?78W!_Ftug8qvcV}Kq=0sC(uF;S8JY6!ytWJ4jJO4F2%fKY>b3W5=m$-d&%ilF# zSKs6g4eK{Jx6RSrn{7Y$Y;;)J zX>Upm8j!5t!9qEYSIEM@+-l+rZZIy;p;WCeZ#3Q7L*xtS-g(LU;Jx;q@!u-^&AaJc z=8Di?$O0h?ge(xUK*$0i3xq5XvOvfJAq#{o5VAnX0wD|hcea2+`HvCmV#zeIq^elT z3M?5cDWeAejUK8S{6utdGQqe&%|sD_5nJbBT)@37xqo{-+{@k!FOyfr+u>dN?|kB+ zV+&azWPy+cLKX;FAY_4%1ws}GSs-MAkOe{(2wA|lfJpw2FY8hlE5(;2sEL*0%V|@^ zO7Z1U!B~U%l73*UL3|l1FxDWxdwPWM({Y(A6I4M3j(Q zU!OCjqw!{rF}vnh10MG|6aDt_kmWNUR2j{q3w*8o4>v}s6CS_bdtRPepi`yU-zB>_ zYsdAWqp#L}o%?1@B}U02)0%R}2^%Lb>l&SU`v^nL7S?G~lXBOlj7a8ezTT|;&d0m6 z6tOmCB7A-Un&wpIJ%@`o?fOhfHvG>1LiC_{C4Z7tr(=gcJ$_+)De+sbmN=euv*9IPabC4tcx1t=>9s8LtZcg)9)VK*$0i3xq5XvOvfJ zAq#{o5VAnX0wD{8ED*B5f1L$XhY_OdOtm=;T~;k2YqDwzS!1dpWK~vmA*)Q)fK*W# zUtLiZAu6)MgveAXL?$b;5UEOpNM(Nbwm5}gT;TcU=eeKf$UjQVFW`9dCGWZS*8A0a z=w0BM1ADxa-eGS&&mdUNt3rPv3xq5XvOvfJAq#{o5VAnX0wD{8ED*9l$O0h?ge>r1 zXaVt_Pju!WWgDo;7Lu7jRpucAzMjf-B-KP*fG7|qAUrMrB4bd?WC|jcIpX31V#t6W z7Z}j?{QK-PPtU`+z;?-d#pfKn<-Pa*n`yqp`y~Za!4Txl6Qi+GX{)^^H--dG0>5o3R2)J~yA6(<|r=*7KN( zcgU$>pQRn`dh7)KsCTDdX<=R-rK$OjCAHHSAHADwlJS=|S6}9=v|6kE*&%(N*T=lW zezk|_54}BVC9}DgO;7GDGKboIyi0a9y}kLIh1(mPN7gj^xPFL!Z$B}jmAblTji+DI zZCY3TjR0qAy#d?JM$y0QL+q+q-y7?EqiodLx?{YvY^z?wxTu}gHGQ|8 zMElWO&4y|-v_I{8_9|r+JFlgpPn_vaPw$1X(X6h&(@Zt1(}{lTcCl}|k~&`@$}jE+ zwUp7>%U~8T=kXMjbV^FKhjzd?WiMu#%|mJ-E0HzQ+-+5H?kWkDzl{i|qWaKlYz=ZV zxrZ#1j&b+c6OC2MBC82qp=Wh2DzjNP^_|(*d+e0bpDR z4y3<1t0(h$4sM-vdY+dl(Ozj=d{z*nW$c2?es~iYD{&1F|W97UCo@NpHucJ1FdLQ z-8!l^a+1)W?5^5GdfmvN#WhgC;C$JVK{ygEi1*4)Zyw{?ywITXXK#2lO1 z<-Bs)6C{PnT0!Y2 zvPPvuu1{7Ak=`JyV71|7C9F1&tN@lH%R&E!ECbFbOM#3mf%-*cF>pFrBq;493k7L; zvH)0{%m-E_^91Fe$y`D58<_*#M!o@-CbI>l$>eK6+Kqe#`l@7>ILtz1rV!;aWCp0m zlIg(IWSXc>LZ%9lo+VR&CCQh9(r7Xnfpy6k zL1`};4O~Y?3DR{W2H2DY1j!F%BycnN0@##{02U*|fxnVr!0*UVU>h<-Q2LDw7Kh!7 z3=*hH1`4g5k_-@}ok@R?n~{FNNYYnOdQJL3eMiz;kfbHipx;P(fj&9u3DijssQ-#| z2Ua58#BTDDt^y9}0&9&Yoq@$jCqel+=?H5}qyxy?NPFN4(hfM2w1xURqz!N%X$@RK zS_#rZq@|$z7ij_9OQL|ONOM8y1!)GHO`3|s=u4UikxnO#VYQB=5y)YrA;@P)13|Kq z)CW0&)B_SySCA$mbwI94Y6CNnT7sl2sR{KvNDW{aQeEsW1*rz&U{Y03&QGcclE$R6 zsIEXNLG=<+5qOVO05&J(1?9w~9B?-&D=1ANWyHr?lF|aL0(G=0mDf_LGm{#AjluP`9Z!*B7x;eK0#?EiGccfB(I?S znB;+aN^*n#0tpBGb&?CXpX7x410)A1C1?Se;}6Rw0=M$#s$mSf7M} zegVlSD9s}o#A$aY=|O#vq!Z*$npTipA!&d=k<>tiq!O#=ASs1NH@oT zQQeUw1@&2y1o$;c3>-ibiR$Dep%CRIBmt;%5>HT?Ph3%5lsE#phz+$r5(~JAn81xZ z<&m#VM(`9!!LmdXtG6bq5a}wS0F&^^lZBj-P(k@Vkp)RJLVyj3Bq%MBK0*B+>2J`V zlRg5cNFT&z6HD)fNb^hYK)peF3tT3>0d|mH3(B$5E08}*e+f$0q?h93L!}o21El9d zOY2F`fYqcwfvWUWQ0^xEAt)V|o`AlU^ceK7q(_3plO96-H`4D=pHO-L^_NEe~Hp7b-YmUID_M>;PkjgiiY)kjEYg-8=iXFz>G zIt}tT>6DDeVXSU1=ZiJ83WI|CYW7)|2)CD@wb?;WU?a!D_>$?|?a^or0v5 zv_nw(C~XIMl(Y?4MT!Oe32Cb!Zvb0>)uhd0b3>#}LZoe_jlc%d2BEGYeJe!iJ83kKrEl3(kt3ZEMS_v#Jt$_MI(sE#RX_+ATMp_D-BrO5j(qiCR zX_26`R9Yx@UtU@uMEbKdA2>sr2P`Gc71fc_93je2q;G&%rP+egL+NYa9_cHw+CT}O z?{%e_LQ8u|Gk_bV>4M~EX&SJmG*wVKDoqie8YF!wkVcveYd?@C0i&gfu=a3i0-SHYwXY&U&|9IYNpxj>-FsceDP5zYTI~q_) zfP_^9q^lqx9V;(Dx0VBRDJvj<6t)!9|p*lQGi7AXD>&^^ngt1#K&k_A<*7w z0NYXnqEZRS(J2MUx)fr?g2|z3cQVkmObW=JL{t?^48m^ywB@G|n^5SY69|Ex;P2(| z4Mgs#^x)-e;5B{a{2mwhKEaK7F&y;IjJCWCUj?MMYJOhEZ$Evsvb-aAW#jN9{aFw^c(c4oUW_-`i}pHsE!7oja<8FRgRb++d&RtbUUo0Nm&|k6Jozt=vEuG~ z<(>Q7{atzH-g19&Pq{z1d)%!wx4YI|!cMy1xKrJ+>JApEw04KP{oHPDTYA@R>eg{9 zyQSQMY=#@IWZ`e8soaFF?tD__I$Q;cTE&ofXb} zXQngJ2{?nCUQS1+h0{RY;8b_YIYpfaC!3RwZes(Sq>ioV4z=Ie&*)zJfqj!EvM<^{ z*@u<=_AVv0y~$o_&!<FAER*lpr3#*=0#VV!L zP|8^atejQ`E16|mviXL-Hvcg1vUKJZ^9(&>9x?ZrTj)@8wYkuoWll6lD$`gJbAZ|1 zY|A#VFW7RkiCIgn$4s+=SKe}v+sanRVwy33c0 z4N4JXxjMs`ORKV*YEEMs%WjM{h8cZ~&PGf1f-=@agM~m&ztk^Sipd=; z1tNo`7}YP843+{_gQb}2!BVSWLB|5sVwwb1Y2yk^>m5`L4i>y%AyCmT`MWIVt6KV{ z(!r8~1*Qd>1yzAMe(Cc^2L*K{f`#nCLQINaDbPMxipl7gK5wl@P}e6|2s8|q3@lKs zN>Gy}SP1wRAmCqly%;&iI^dA;=hdUCK- zEm(+&2$llTe#zf$ppdWAf+hcI$7%QlZ62(P$r~&MzVJ)6gQd@AOA=H`SYTRAk)SFN zr<1kDK~+pazmzgq3M3Ae{Ko0G+?X=Kx`J0IOd{mze z<+rOCzZu5(Ei9(KzmX-ah8RNIgm;!!ne!mpym-6|gJbvkOBl+y@pYNXj)sM;Sf5N|x zF=4(ggI`MLmx6aQEhdexOYN6Fx4-1RE}36S>X&@ih)L+{68I(mt{CI`y3h9w|0W(| z`L(8B`h1h{ogzl@Ynfl7eu?-c-zNjZgSvqKSP2aEbwm8pV81lbFZK0H{{24C%hz@H zOWpjE?;e59zOIvB>gbm`_@#D!sjXjX_chm^<@=fMX1l&+^5&Pfq#X)GwDXMpU#!Mciu}L7kI#D9=zsV^05TF`3wUa zyj47ZU=IHU!%5y~Z@4$W>*aO!+IY>p`d$sMqF2f*>_vDvyf81dm&9{CmFEMzai6&l z-Cx~n?$7RN_n3RY-NkCVTix~U3U{G9+nwf4aAVw|JQ~o$?dZ00o49q|YHoSA1dj)_ zaPzp?+zf6?H<4?(%=yUU0Z*L=&TZ$4bKW`W{NU{6k$_EfwX?=q>dbRyIbS+soiBJa zpf}6nbamP}QBFgrmQ&d&!y^KbPA(_2lh#S*c#iH69uIh7Kc-9U-|QRqFZLOh&OUA* zviI2A><#uRd$B#op1~T~lkCyzH-G+GTC!k%1M~LTk1)&6;4vSVOITtgO|8#|K(j zO{}_BHLJW;!YXLxv9j^_KuX?kZ&}RzXudL^GQ)ge-sUla^K`Cx()_{PYwlph%uVJR zbE!Fxwdb*dFU_&$7fdz>nZ3=fydOKi8D%y!Ynhcnbu5Zdi15K zo5Xl$yr55vC&oSFrg6zQYaBNY8{Zq-jg7`?V~H`>m}yKl#uy`vfkw2^#b|3p84ZnE zMr9r=C~8C+xs1$4S|gd^8M;AuwBV)wgvSeR>6dxL;Dmly|6bp&Z`4=oOZ2(=Odc;7 zqmR%B>d|@^y&cbqXsFlLtLSC*VtRf(T+gDX)0687bUvAe_Fj9bJ<;xIH?>RJS?#!X zNZX@r(>7?Uw8h#So*gks8?6o3257ys&RQF-nO2|22`Xx(w8C11mQ%~5rO}dVuBK^{ z`c{3eK2q5m4Grt z>8o^CIw&ob#!4Nfs!~oVt`t=ADA|+@N=hY>V)6MtKC;*BPxd>z!>+Om>?d}F?PELH zX110sWAoWpYziC4MzX=I59>zn(VO%VJxh;Mo?$|_(~Tk+nA2tgDQzl{*cNF&rM@tD zNR`N6u;wDL_(fJBj4&SpG#-S8z8-<$#(aRnW1w6j&}|fH^N1zy!nS$DlE@Ku5`o+{ z4_WelMVp5#iJZ<(D3I3XF-sz+vONLb+6D63jzAvU7RY5=0$FTRAj~!dEL#^yV?)QF zXRAUAw-tc_<~0C250IV<^fvD-I-Ao0Jc~w11h(ACYAmEq>?A+}bTq*F9W^+A-s>ysI#@hH&!G+#o9vKF z2<`PY00u^EFGCAxXAKj|c8tfkImkYNw&pZ|0+xkt8zQ&iiBi0-4GRa5?EWfU=49LDV&2JciF9!-Z{Z#0aS|s{xR~ z#*HlAWkBRcN&x_cHrCJr^EA{}2y-@!7F7+*h5#)OfUXj#ufY|quZ;zXtQM$eMFNz? z0J2x0u09K(ECNU~1?m`^0qT2zd{>~hRU4pzlkyHyR8aCxP9as-;bK;2;H%XMY_pp2K-5)ZHvklFH&yi#LaNHZg?MU>kg6Cn z0Bk>i78Iy#fvqa58->0pmaN*bXPy)i_ts6Pg%M*(D~Kn1-SK!MZZ=_5ia zue}B+zXPNN0_98#pn!?X(Y`_|Yia-nca5^-qmas&&jGB5*tzJuBXVg2u5xL0htP@6 zK_Zvp>1hJ$Wq{mHpri>WTvCPIl$8Gx$`YmwU~tSOXe}WXx4>hIs|rXIY+1|%s~1yl zi2aDZAf8iU4go0X0GMG>M7t%VA_`pOA_R<5Sc4-ktaTT4g(=vrknu=Jg|wCc0xhPX z1&3LX9TEEx-BCm?V3h=D-vM~=oGbI2a3cBD3S!0lOz0x5(*Uz9KsyYO<_P4oz?Au{ z%pj>_0GRR=Vcix|gbuck(7+lI1lnL;>z=5~Yvu!}u(7;yIU(iYeI^1%A%LSH93c>9T>|K702CY}jKKM2v_1)CMho04qwx$RwiqBc7s$YSy8v)!$Y8-m&A`84 z38masAiV`9PjA8A)9bK@^x9m}%tYTV|0ZEw1n3O`T6Tb(K_IOKwn}Su0!e|pQ(F3k zkkVLx0Q5cpbs|8TERfpz8*v<9!ZuTDBgI}tM=}4#VqFIq*#HV`E0r88q?Fdr028c| zlH~-6LIX}=!5O5mo(N?MeIS5miwcQU6G(1-Ks-id0;q6hd0v%}l3DK&=Kvi6OUi3lK}^-@R)t+N0VP9>o-A0!EUF@g1`kP=w;5fcDf3xM2T zz_Ts`OclW3VdhcrXxE0rb*-DC&eiJzv~2*{6L4&3_>T1>NIKkd90lyj^D2eXww@x+ zAi4t-a9*22TeYkkLTQ=30SY{%En5ht1t)77+d)#20c5z}8P*;l8Aeusnh!vW3h35; zfCl@~l_DU~Q39H^4`9OSY1$5uWbg^q+AAd0SPxL)(yDSlAt}}YfC=89sBlpf+D<5$ zbp&7}1ZWEYa$f;z{QxlGE=GB04R7NjLxIS$br@hA1*p(yWO<%K4cR&*mgetbsWuZ! za!R4PY#kI!?TJ`2KF&awUn5rk$zIAY*A6h4!aCX(!s64P%p8 zEIZ8ZY6o~f?pAHRwnAH|&DN%A6SNp@DDTJZp>@<+YfZI!S~XgQbz|*Vb5@^KXXROO zR)B@G%q$H{!feLq2l|perg!NzdV!v#hv^;~OV`uobUvL$C)3e%7ZoxikSGBF$ zjQ4<#;_qtZ)MEUbOindSO~v2TOqD9{l^4n*$e;CT?ey)HcB(bTK!RrShxMR^WH4lkpZ(o5(W9&z9DXvgpFZTE_M z&OPoPba%O1c<#eecdk3boybQy#JEG;K5kc@<=)J#$KxF3+~RJ2Hy6)&NbM%(F%Igy zcV0S=**fPp=eqN=^AnG8eD7>?zGZ!Rw!;GFD<0t(;|zEDJ3X8ZJl~-qd+*eADmo>d zf=+H)#>v9t8%Z6QvF2FQc^o5P z4Yqn)U92`%Q=a2c%_?UVvm$whLuM-tdtoKvSq{wnV7@dTn|IA?<^>+TILuD)EQeS= z7Gk}*+?>y27n9A=<}kA#&vIzbqZSR!8a%t91Ru{3&f^tn%p|65GUEf!Zg_0mHLe*K zcy_~KV~-JQtT&b$^Nm?NN->&eH1s!m7#)ljJVsH|sAQDl`3!lCtVTK>p>PdV|D?ax zpXv{IeBzRRMn9(S*LUihcywa1{*69OAFq#O=XpLuwBA{7tvBJ>3{~_pdJ#Q>XEQv~ zGw3OJY(m#1Hcxw_{i!|BZt>W}8SNPTL|@aV^ge%6|An5WN9jJglk!LuT};2B)983Q zk`AKLv@>ljNsy1T$I1m9$)gz}KWDy%d8YG4>*cw33~mx}DR=`lxZ@90{9GQ)=}{pw zPnsx0?w~Y5?*#e@3S{U>6Q~LDcxxb1Nsp8vBT^b?LMF2`PN@L;aWZ7eN@EQI@>sS3 zN&5lESe22Qg_O<#N1I_tZ5Wc^ag8#$_Um}$jNXny=+ynswQYm>_ zUsycq(;*oA4($j6PFPxy|fB}{=8KHxXa29F@WO!NPQi`S7tO@9OPCxK#1DnCu8k+rU z@-67UGF~IqEJzuf)+`;mf-Q;Fk*I zb4y=RJpIWAWUES(*&k3JH)utgWUK&r5_^s$+4xgxC(-3ZoJV=ic{-uzO&In|RDCGO zDr=0S&^n1?z;=nkXX6p;^8|YS`Eh(a-3E}EjG4MrZwE3}S0ge1pMOVy=ENWUa9;e8 z4B68>iT!U_kFcJ?OguxCZ#IHREIX*Rz8(#u z>qG+{Y90e9c>tL3K15Fnjaz|yL6KMvNAX67vyt9VIC@TWir0EpsDG`FN8<1p>6O|E ze%X@GIZ*v@&eHRVVp#&;Yb;s<%t1F=F$^28|a_VlEBBt zLZrgKg7Fm1$E`qoWTNjs(kQ4Obp!Q74PEIW9~CFgE|_=yyYU^!zblXhF8xk7g8aZp zhh+RaC*-h#c;7%{-B<7id|$?w?r0I8}-8BgYpfu44UKh4C^-;s+0Z|gIVT5Tjj$G>IZ+1z3sK!1xI1l}~z z6K|@eP?kpnZLkmDYVxC8`pONG-@CThC*-|9!4?JwCU=t_1kM;!aHGV|$ z1XiIBj$r)Wz|Vb8AAT0 z^}wAvUgMqYdz5iJsImyazSXLsEaSCYui|>^<;WFMXD8%GWyX<{TRqARXp3c;e*l& za{%a9sQ9gP1>Fzwa*HAvw&>+DK7f~*cv{O;wC*x`2i99^$w-DbFFrsB)Xq&-C{KOreT2p8%BkQb`x&kOGLT*=Xh*TyJmdXGyE3b)+-GB~L&kCYsd3me%tqF4j%JRt z%Q~r?^6FZpmbuejW2{oI+h^6c%1OPPQ_{N0``>jZ%6@PE#Yeoabq?55)nC-iPGRS= zdBM2FLue87wUdGO>$hdG>Rzj)oz}|ej4|#xb?7x#Uf-hZvcEE`D2tq<&T?y!73&^X z54-Etv;3`ol6_Q7XJ%2Ct0~--`a`9PQqKHa8N&uS#CfJx=kM1^)ST8tXOq?18SE@{ zI;%~r)Yb-P65FqhFy^uuPGA0Jnx5@4sndx4=~T6sTG`kgC%2Q8r8L(VFRUhxW0X`M z+HLu`fxAXi`xh&Tk=Xv;o@Z}luax3!5i7{L+R^q1d!QOH2iaYCCuRb6RUHCZ1VnkF zwF%}CClkh4ND55wH^4C6Py9^-!`} z9hHQbMWCz~Sp>%S<`?SMMo-?`+*=*5F5vIsw?!_2`GEaoUSSO}k3eZ_Zc@HccZfU! zbC&wd{8GE8PceHd1I@0MqvTe*n^DRwvzAc-vk24*W-g^U`&KO}@(7fz?4#Pjc&AJ^ z9xInwBI6g9jkPn*@NUJv>Qw%Q{;hIOiDs5D-I!>M67T5rPoop>VyVkk8ug6oEDsx@ zw&ZW>d3mqtCL^OW+eoFXFlwWZYdV9Tv-bmyWXsQ3boC20XyUC9HdpQM41Nu?q6wu%KOb6RVR)H$} zc?CJNF#Z;uM05BUgkRN9>hXW)-y8Lr`jC$mxusm@XMRFCtbDI*S2ilEl_knt9)p^! zjNw;tpc1WgQQGntR0E}^Qb{ST6jAaiIe8Q+jgnMx6-|-YTlxcg&K|M5>^i&1PP1d| z0Ncg3vh{2QTgYa!X>06N$153#gF^k96KGIh_ zM)82&rdQ}DNuobU8h`Imt8++eElc2~y`6*fwr>gK21r}f4kuy zQE~|sAPy^&@+hT1K+IK^#-bF3a1qdZ!pCTXB>e!psS3*ku#EdGgg;~n!moVHEv`Ke zaS2xB4=jFc1jcvCiBN#iRq`MdO5xi4`7YMu-=_r#e3pkY`PmJFB?Pc#(h}Fcg@En> zSz3VJp@pM+tglWN8n4tQ{=`k`V-m;>#rNkTl%kEBq8*ZfON77a=ZVU1F4eMqGhyMZq#Xu8<3Bcto5Z zSy@;%z?uLEjbI6b66MTT5qHfe<0qf+U@`w_R;(L|7=aj$7>1~gfXJSVlT*l;R3c*! zx=aS(Dsin4BKA+yerm0EI=lmQHnEXN0d4s;@pP6 z>xzxFMYKV*MzjJ*M^I{k(o>Y0Bbp(aBI4Q|HhG8rtB;Kvgn9ODIOi^8%J zDoy;=MJOL}R31hg!jGe?Ne8S0ux4Laq8mzkt?M8rhNX+2+l|;|y#;MfSf)hf4#ajF@_1zQ z8EG}Ff~_22Vzn$-Gab~qP?+lUjH^uw>b$Uo z@#t~~D5LL56G7+#OLPvr%}8T#L!+HKxW7WMN)A}2!@7|mpxcaqa&B0bhGn#~oj

    }sB?3wC3+aN_&wQF7i`e#}@N*sVKs~WTca|=3r(LqG7%;sTI&3$IZS6iREI>Rsx zO|c_PCu8+|ZwRXlrLI%o4nsMi%TGZQWlQ7poq`?7)b5Wims&J$C8q}JzF z$-H?o;stwxOzrD4fLD|dz*b<336|#s%`2lA{q+Y6DFrCmeR^PKLk9+$NP>7XU%xDA zk7sbC7Mp#~k|*b%4b#N)C0w798z+@Vl+JB{(K91`2y4Q>q1)y+nd_a)+&(;%roScR zuh;0Tq?RLmW2b&dj2&z)E)vP9iUhDD^|hG zh7RCyJ3f`9HwsZg+t9(QqqI60bj9!g)UBYK)(U%hm zge_@G>VXf76tU~gD9a2&J{`{HGJm8KubymyyQ*2{@{;w)JaK{%e^F+_v+cyB=pqi6 zUYVE4+7>dU3;&*8i5l2&WFT^R;3jfw!K@QME)%8L_b8g?KgNvjC zwC6q@Ftea}n)MwWu}?2YbeQNr?0jZrK+`Hib8Lu~DsF;6g{!t_8IIs%?gyh&w0^Vo zl{e5H;itZE5?Uqds>o9WjCN1=wnVCj$6sorG5I{#3 zMF%t^_I)})4+@|MztRCQ3z~OghvdRTsYJgw3mdmGqMu_(cSW8R*-lA!lD!=Jeny0a z2En_g3l=+AM3~lLow<~}d**d}6BG82T?wrfQ+6&1)xJ)sQ;rMZjMCujCH2&m$GB;S z?>D)Ov{+NDu#00zu5Ww1D`O-nORK1PBkzTwN?$Q9;|a9_d5czZ6}D%)jrVg%4Plac zfWxE!of9ZJpn0wD(*dq5;OE}<>42F9&F%kC>n3&g_)WeJ;X}h6GgH|_Mws}@48zBt zIm28T*stv%c*#KTd6)RT@&k-*1Bp}c>T?aqNo=ECw`I3=Wdh1nlPiy9hB39JZ^_&< z`?M3TSUUFhv{WQ)o~+un_}v`cOSy-{4GdkIDveCCQ;Wp|McVHq;)mYXFqt^x<>*vF z)DmTak*LZ<5@5#ZN6`VzQGK5d5Ievxx9!sbGYgvA8^UIL$F!?Xp*A@EOH-+UU6b*3 z#`^2grFnHbRn4^9=|GO>3|Q=ZetLFG5r@xg{9+i)1pC^UCr1T!QK~@zY_ages56`v ztZm)*!A1O{7kzrX&AFSnwa2GyCN5WU807LdFIm#~&(b9U4if`(dQo&h^G)BUgYjSP C9HsXF literal 0 HcmV?d00001 diff --git a/db/validate.go b/db/validate.go index 406d4bb..c9dea7f 100644 --- a/db/validate.go +++ b/db/validate.go @@ -2,20 +2,71 @@ package db import ( "fmt" + "os" + "time" - "crawshaw.io/sqlite" + "crawshaw.io/sqlite/sqlitex" + "github.com/AdamSLevy/sqlitechangeset" "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/flag" ) // ValidateChain validates all Entry Hashes and EBlock KeyMRs, as well as the // continuity of all stored EBlocks and Entries. It does not validate the // validity of the saved DBlock KeyMRs. -func ValidateChain(conn *sqlite.Conn, chainID *factom.Bytes32) error { - eBlockStmt := conn.Prep(SelectEBlockWhere + `true;`) - entryStmt := conn.Prep(SelectEntryWhere + `true;`) +func (chain Chain) Validate() (err error) { + // Validate ChainID... + read := chain.Pool.Get(nil) + write := chain.Conn + first, err := SelectEntryByID(read, 1) + if err != nil { + return err + } + if !first.IsPopulated() { + return fmt.Errorf("no entries") + } + if *chain.ID != factom.ChainID(first.ExtIDs) { + return fmt.Errorf("invalid NameIDs") + } + + // We will use a session to determine if recomputing state results in + // any changes. If the state is uncorrupted, the session should have an + // empty patchset. + sess, err := write.CreateSession("") + if err != nil { + return err + } + sess.Attach("entries") + sess.Attach("addresses") + sess.Attach("nf_tokens") + sess.Attach("metadata") + defer sess.Delete() + + // In case there are any changes, we want to roll back everything. We + // don't fix corrupted databases, at least not yet. + defer sqlitex.Save(write)(&err) + + // Completely clear the state, while preserving all chain data. + sqlitex.ExecScript(write, ` + UPDATE addresses SET balance = 0; + DELETE FROM address_transactions; + DELETE FROM nf_tokens; + DELETE FROM nf_token_transactions; + DELETE FROM eblocks; + DELETE FROM entries; + UPDATE metadata SET (init_entry_id, num_issued) = (NULL, NULL); + `) + chain.NumIssued = 0 + chain.Issuance = fat.Issuance{} + chain.apply = chain.ApplyIssuance + + eBlockStmt := read.Prep(SelectEBlockWhere + `true;`) // SELECT all EBlocks. + entryStmt := read.Prep(SelectEntryWhere + `true;`) // SELECT all Entries. + + var eID int = 1 // Entry ID + var sequence uint32 // EBlock Sequence var prevKeyMR, prevFullHash factom.Bytes32 - var sequence uint32 - var eID int = 1 for { eb, err := SelectEBlock(eBlockStmt) if err != nil { @@ -23,10 +74,10 @@ func ValidateChain(conn *sqlite.Conn, chainID *factom.Bytes32) error { } if !eb.IsPopulated() { // No more EBlocks. - return nil + break } - if *eb.ChainID != *chainID { + if *eb.ChainID != *chain.ID { return fmt.Errorf("invalid EBlock{%v, %v}: invalid ChainID", eb.Sequence, eb.KeyMR) } @@ -62,8 +113,8 @@ func ValidateChain(conn *sqlite.Conn, chainID *factom.Bytes32) error { } prevKeyMR = keyMR - for _, ebe := range eb.Entries { - e, valid, err := SelectEntry(entryStmt) + for i, ebe := range eb.Entries { + e, err := SelectEntry(entryStmt) if *e.Hash != *ebe.Hash { return fmt.Errorf("invalid Entry{%v}: broken EBlock link", @@ -79,23 +130,56 @@ func ValidateChain(conn *sqlite.Conn, chainID *factom.Bytes32) error { e.Hash) } - if *e.ChainID != *chainID { + if *e.ChainID != *chain.ID { return fmt.Errorf("invalid Entry{%v}: invalid ChainID", e.Hash) } if e.Timestamp != ebe.Timestamp { - return fmt.Errorf( - "invalid Entry{%v, %v}: invalid Timestamp ebe %v e %v", - eID, e.Hash, ebe.Timestamp, e.Timestamp) - } - - // Attempt apply entry as fat entry. - if valid != false { - return fmt.Errorf("invalid Entry{%v}: marked as valid", + return fmt.Errorf("invalid Entry{%v}: invalid Timestamp", e.Hash) } + + eb.Entries[i] = e eID++ } + dbKeyMR, err := SelectDBKeyMR(read, eb.Sequence) + if err != nil { + return err + } + if err := chain.Apply(eb, &dbKeyMR); err != nil { + return err + } + } + if sequence == 0 { + return fmt.Errorf("no eblocks") + } + + changesetSQL, err := sqlitechangeset.SessionToSQL(chain.Conn, sess) + if err != nil { + chain.Log.Debugf("sqlitechangeset.SessionToSQL(): %v", err) + return + } + if len(changesetSQL) > 0 { + defer func() { + chain.Log.Warnf("invalid state changeset: %v", changesetSQL) + // Write the changeset to a file for later analysis... + path := fmt.Sprintf("%v/%v-corrupt-%v.changeset", + flag.DBPath, chain.ID.String(), time.Now().Unix()) + chain.Log.Warnf("writing corrupted state changeset to %v", path) + f, err := os.Create(path) + if err != nil { + chain.Log.Debug(err) + return + } + if _, err := f.WriteString(changesetSQL); err != nil { + chain.Log.Debug(err) + } + if err := f.Close(); err != nil { + chain.Log.Debug(err) + } + }() + return fmt.Errorf("could not recompute saved state") } + return nil } diff --git a/go.mod b/go.mod index c66119b..77cd176 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( crawshaw.io/sqlite v0.1.3-0.20190520153332-66f853b01dfb github.com/AdamSLevy/go-merkle v0.0.0-20190611101253-ca33344a884d github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 + github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 github.com/Factom-Asset-Tokens/base58 v0.0.0-20181227014902-61655c4dd885 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/gocraft/dbr v0.0.0-20190131145710-48a049970bd2 @@ -20,6 +21,7 @@ require ( github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.3.2 + github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 ) @@ -27,3 +29,5 @@ require ( replace github.com/gocraft/dbr => github.com/AdamSLevy/dbr v0.0.0-20190429075658-5db28ac75cea replace github.com/spf13/pflag v1.0.3 => github.com/AdamSLevy/pflag v1.0.4 + +replace crawshaw.io/sqlite => github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c diff --git a/go.sum b/go.sum index c94b580..533d587 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,16 @@ github.com/AdamSLevy/jsonrpc2/v11 v11.3.2 h1:McSW/pP7K0/Ucjig6AJwW7Khph/XOMYhSB8 github.com/AdamSLevy/jsonrpc2/v11 v11.3.2/go.mod h1:7fNjH6BXM0KVswWqj+K/mnOS8wiSke0sE8X46hS+nsc= github.com/AdamSLevy/pflag v1.0.4 h1:oykgyxDWo391JRRNOLr892UzaK2SmLiAvza8OBLX24U= github.com/AdamSLevy/pflag v1.0.4/go.mod h1:UeCxQpw/gAoTUVBfjr8n7qcGZZNmECgohVFOAdlRMtA= +github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c h1:n+ha40YmIj3rnMvg4KkNUmPRHC+U0axIZFe51LVJdaM= +github.com/AdamSLevy/sqlite v0.1.3-0.20190729192944-6cbd592f144c/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730214510-e4fb7efdb5d6 h1:hVlVweZFMmcyprlwc+JbVYjF4PUV6ih5MTNVlBgHR1g= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730214510-e4fb7efdb5d6/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730223213-413396883909 h1:DPtIFlplKA4W+PsLD0JYyTMf4erlgiQ14nZUn1WwysY= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730223213-413396883909/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730234737-ed3ae2ef8019 h1:ehFc1GJ874RQOAQRBtyOAf4G1PbV7Gcqp3KHyNoECTU= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190730234737-ed3ae2ef8019/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5 h1:2gBp1whWHlQvpYS9vdlmGKYBvs1qwzrxmov8KjUnBGE= +github.com/AdamSLevy/sqlitechangeset v0.0.0-20190731000048-d57789e63df5/go.mod h1:X5d1YVaSKMYV0QrZbPKcsWhE4k3wiWP/BDUbz2dDi1M= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= @@ -176,6 +186,7 @@ github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= 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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= From 0c00c596d8dd799cbc004e8e1d4609ea9243f3e4 Mon Sep 17 00:00:00 2001 From: Adam S Levy Date: Thu, 1 Aug 2019 12:44:19 -0800 Subject: [PATCH 009/124] refactor(db): Improved org and fewer exported funcs Any function that modifies the database is now a member function on *Chain. Fewer functions are exported now to reduce the API and reduce the chance for misuse of functions that should only be called in the context of Chain.Apply(). Additionally functions that run UPDATE are now more consistently named Set...(). Many top level comments were removed as a reminder to re-write the docs later. Finally a small modification of the order of closing db connections results in the shm and wal files being automatically removed. --- db/address.go | 23 +- db/apply.go | 251 ++++++++++++ db/chain.go | 379 ++++-------------- db/{db_test.go => chain_test.go} | 5 +- db/db.go | 83 ---- db/eblock.go | 8 +- db/entry.go | 29 +- db/gentestdb.go | 1 - db/metadata.go | 45 ++- db/nftoken.go | 16 +- ...f56169f94966341018b1950542f3dd.sqlite3-shm | Bin 32768 -> 0 bytes ...f56169f94966341018b1950542f3dd.sqlite3-wal | Bin 943512 -> 0 bytes ...ec561e7874a7b786ea3b66f2c6fdfb.sqlite3-shm | Bin 32768 -> 0 bytes ...ec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal | Bin 1256632 -> 0 bytes db/validate.go | 2 +- 15 files changed, 400 insertions(+), 442 deletions(-) create mode 100644 db/apply.go rename db/{db_test.go => chain_test.go} (87%) delete mode 100644 db/db.go delete mode 100644 db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-shm delete mode 100644 db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-wal delete mode 100644 db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-shm delete mode 100644 db/test-fatd.db/b54c4310530dc4dd361101644fa55cb10aec561e7874a7b786ea3b66f2c6fdfb.sqlite3-wal diff --git a/db/address.go b/db/address.go index fde42dd..d4c0b6b 100644 --- a/db/address.go +++ b/db/address.go @@ -7,10 +7,8 @@ import ( "github.com/Factom-Asset-Tokens/fatd/factom" ) -// AddressAdd adds add to the balance of adr, if it exists, otherwise it -// inserts the address with balance set to add. -func AddressAdd(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) (int64, error) { - stmt := conn.Prep(`INSERT INTO addresses +func (chain *Chain) addressAdd(adr *factom.FAAddress, add uint64) (int64, error) { + stmt := chain.Conn.Prep(`INSERT INTO addresses (address, balance) VALUES (?, ?) ON CONFLICT(address) DO UPDATE SET balance = balance + excluded.balance;`) @@ -20,20 +18,18 @@ func AddressAdd(conn *sqlite.Conn, adr *factom.FAAddress, add uint64) (int64, er if err != nil { return -1, err } - return SelectAddressID(conn, adr) + return SelectAddressID(chain.Conn, adr) } -// AddressSub subtracts sub from the balance of adr, only if this does not -// cause balance to be < 0, otherwise an error is returned. -func AddressSub(conn *sqlite.Conn, adr *factom.FAAddress, sub uint64) (int64, error) { - id, err := SelectAddressID(conn, adr) +func (chain *Chain) addressSub(adr *factom.FAAddress, sub uint64) (int64, error) { + id, err := SelectAddressID(chain.Conn, adr) if err != nil { return id, err } if id < 0 { return id, fmt.Errorf("insufficient balance: %v", adr) } - stmt := conn.Prep(`UPDATE addresses + stmt := chain.Conn.Prep(`UPDATE addresses SET balance = balance - ? WHERE rowid = ?;`) stmt.BindInt64(1, int64(sub)) @@ -44,7 +40,7 @@ func AddressSub(conn *sqlite.Conn, adr *factom.FAAddress, sub uint64) (int64, er } return id, err } - if conn.Changes() == 0 { + if chain.Conn.Changes() == 0 { panic("no balances updated") } return id, nil @@ -75,9 +71,8 @@ func SelectAddressID(conn *sqlite.Conn, adr *factom.FAAddress) (int64, error) { return stmt.ColumnInt64(0), nil } -func InsertAddressTransaction(conn *sqlite.Conn, - adrID int64, entryID int64, to bool) error { - stmt := conn.Prep(`INSERT INTO address_transactions +func (chain *Chain) insertAddressTransaction(adrID int64, entryID int64, to bool) error { + stmt := chain.Conn.Prep(`INSERT INTO address_transactions (address_id, entry_id, sent_to) VALUES (?, ?, ?)`) stmt.BindInt64(1, adrID) diff --git a/db/apply.go b/db/apply.go new file mode 100644 index 0000000..3fa14e9 --- /dev/null +++ b/db/apply.go @@ -0,0 +1,251 @@ +package db + +import ( + "fmt" + + "crawshaw.io/sqlite/sqlitex" + "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" + "github.com/Factom-Asset-Tokens/fatd/fat/fat0" + "github.com/Factom-Asset-Tokens/fatd/fat/fat1" +) + +type applyFunc func(int64, factom.Entry) error + +func (chain *Chain) Apply(eb factom.EBlock, dbKeyMR *factom.Bytes32) (err error) { + // Ensure entire EBlock is applied atomically. + defer sqlitex.Save(chain.Conn)(&err) + + // Save latest EBlock. + if err = chain.insertEBlock(eb, dbKeyMR); err != nil { + return + } + chain.Head = eb + + // Save and apply each entry. + for _, e := range eb.Entries { + var ei int64 + ei, err = chain.insertEntry(e, chain.Head.Sequence) + if err != nil { + return + } + if err = chain.apply(ei, e); err != nil { + return + } + } + return +} + +func (chain *Chain) applyIssuance(ei int64, e factom.Entry) error { + issuance := fat.NewIssuance(e) + if err := issuance.Validate(chain.ID1); err != nil { + chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, err) + return nil + } + // check sig and is valid + if err := chain.setInitEntryID(ei); err != nil { + return err + } + chain.Issuance = issuance + chain.setApplyFunc() + chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) + return nil +} + +func (chain *Chain) applyFAT0Tx( + ei int64, e factom.Entry) (tx fat0.Transaction, err error) { + tx = fat0.NewTransaction(e) + valid, err := chain.applyTx(ei, e, &tx) + if err != nil { + return + } + if !valid { + return + } + + // Do not return, but log, any errors past this point as they are + // related to being unable to apply a transaction. + defer func() { + if err != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, err) + err = nil + } else { + var cbStr string + if tx.IsCoinbase() { + cbStr = "Coinbase " + } + chain.Log.Debugf("Valid %v %vTransaction: %v %+v", + chain.Type, cbStr, tx.Hash, tx) + } + }() + // But first rollback on any error. + defer sqlitex.Save(chain.Conn)(&err) + + if err = chain.setEntryValid(ei); err != nil { + return + } + + for adr, amount := range tx.Outputs { + var ai int64 + ai, err = chain.addressAdd(&adr, amount) + if err != nil { + return + } + if err = chain.insertAddressTransaction(ai, ei, true); err != nil { + return + } + } + + if tx.IsCoinbase() { + addIssued := tx.Inputs[fat.Coinbase()] + if chain.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Supply { + err = fmt.Errorf("coinbase exceeds max supply") + return + } + if err = chain.insertAddressTransaction(1, ei, false); err != nil { + return + } + err = chain.numIssuedAdd(addIssued) + return + } + + for adr, amount := range tx.Inputs { + var ai int64 + ai, err = chain.addressSub(&adr, amount) + if err != nil { + return + } + if err = chain.insertAddressTransaction(ai, ei, false); err != nil { + return + } + } + return +} + +func (chain *Chain) applyTx(ei int64, e factom.Entry, tx fat.Validator) (bool, error) { + if err := tx.Validate(chain.ID1); err != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, err) + return false, nil + } + valid, err := checkEntryUniqueValid(chain.Conn, ei, e.Hash) + if err != nil { + return false, err + } + if !valid { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, "replay") + } + return valid, nil +} + +func (chain *Chain) setApplyFunc() { + // Adapt to match ApplyFunc. + switch chain.Type { + case fat0.Type: + chain.apply = func(ei int64, e factom.Entry) error { + _, err := chain.applyFAT0Tx(ei, e) + return err + } + case fat1.Type: + chain.apply = func(ei int64, e factom.Entry) error { + _, err := chain.applyFAT1Tx(ei, e) + return err + } + default: + panic("invalid type") + } +} + +func (chain *Chain) applyFAT1Tx( + ei int64, e factom.Entry) (tx fat1.Transaction, err error) { + tx = fat1.NewTransaction(e) + valid, err := chain.applyTx(ei, e, &tx) + if err != nil { + return + } + if !valid { + return + } + + // Do not return, but log, any errors past this point as they are + // related to being unable to apply a transaction. + defer func() { + if err != nil { + chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", + e.Hash, chain.Type, err) + err = nil + } else { + var cbStr string + if tx.IsCoinbase() { + cbStr = "Coinbase " + } + chain.Log.Debugf("Valid %v %vTransaction: %v %+v", + chain.Type, cbStr, tx.Hash, tx) + } + }() + // But first rollback on any error. + defer sqlitex.Save(chain.Conn)(&err) + + if err = chain.setEntryValid(ei); err != nil { + return + } + + for adr, nfTkns := range tx.Outputs { + var ai int64 + ai, err = chain.addressAdd(&adr, uint64(len(nfTkns))) + if err != nil { + return + } + if err = chain.insertAddressTransaction(ai, ei, true); err != nil { + return + } + for nfID := range nfTkns { + if err = chain.setNFTokenOwner(nfID, ai, ei); err != nil { + return + } + if err = chain.insertNFTokenTransaction( + nfID, ei, ai); err != nil { + return + } + } + } + + if tx.IsCoinbase() { + nfTkns := tx.Inputs[fat.Coinbase()] + addIssued := uint64(len(nfTkns)) + if chain.Supply > 0 && + int64(chain.NumIssued+addIssued) > chain.Supply { + err = fmt.Errorf("coinbase exceeds max supply") + return + } + if err = chain.insertAddressTransaction(1, ei, false); err != nil { + return + } + for nfID := range nfTkns { + metadata := tx.TokenMetadata[nfID] + if len(metadata) == 0 { + continue + } + if err = chain.setNFTokenMetadata(nfID, metadata); err != nil { + return + } + } + err = chain.numIssuedAdd(addIssued) + return + } + + for adr, nfTkns := range tx.Inputs { + var ai int64 + ai, err = chain.addressSub(&adr, uint64(len(nfTkns))) + if err != nil { + return + } + if err = chain.insertAddressTransaction(ai, ei, false); err != nil { + return + } + } + return +} diff --git a/db/chain.go b/db/chain.go index 8b74067..f34279a 100644 --- a/db/chain.go +++ b/db/chain.go @@ -2,6 +2,7 @@ package db import ( "fmt" + "io/ioutil" "os" "strings" @@ -10,16 +11,22 @@ import ( "github.com/Factom-Asset-Tokens/fatd/factom" "github.com/Factom-Asset-Tokens/fatd/fat" - "github.com/Factom-Asset-Tokens/fatd/fat/fat0" - "github.com/Factom-Asset-Tokens/fatd/fat/fat1" "github.com/Factom-Asset-Tokens/fatd/flag" _log "github.com/Factom-Asset-Tokens/fatd/log" ) -// Chain combines a READWRITE sqlite.Conn and a READ_ONLY sqlitex.Pool with the -// corresponding ChainID. Only a single thread may use the Read/Write Conn at a -// time. Multi-threaded readers must Pool.Get a read-only Conn from the and -// must Pool.Put the Conn back when finished. +var ( + log _log.Log +) + +const ( + dbDriver = "sqlite3" + dbFileExtension = ".sqlite3" + dbFileNameLen = len(factom.Bytes32{})*2 + len(dbFileExtension) + + PoolSize = 10 +) + type Chain struct { ID *factom.Bytes32 TokenID string @@ -39,17 +46,19 @@ type Chain struct { *sqlitex.Pool // Read Only Pool Log _log.Log - apply ApplyFunc + apply applyFunc } -type ApplyFunc func(factom.Entry) error - -// Open a new Chain for the given chainID within flag.DBPath. Validate or apply -// chainDBSchema. func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.NetworkID, identity factom.Identity) (*Chain, error) { fname := eb.ChainID.String() + dbFileExtension path := flag.DBPath + "/" + fname + + nameIDs := eb.Entries[0].ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + return nil, fmt.Errorf("invalid token chain Name IDs") + } + // Ensure that the database file doesn't already exist. _, err := os.Stat(path) if err == nil { @@ -63,13 +72,7 @@ func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.Network if err != nil { return nil, err } - - chain.Head = eb chain.ID = eb.ChainID - nameIDs := eb.Entries[0].ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { - return nil, fmt.Errorf("invalid token chain Name IDs") - } chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) chain.DBKeyMR = dbKeyMR chain.Identity = identity @@ -77,15 +80,17 @@ func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.Network chain.SyncDBKeyMR = dbKeyMR chain.NetworkID = networkID - if err := chain.InsertMetadata(); err != nil { + if err := chain.insertMetadata(); err != nil { return nil, err } + + // Ensure that the coinbase address has rowid = 1. coinbase := fat.Coinbase() - if _, err := AddressAdd(chain.Conn, &coinbase, 0); err != nil { + if _, err := chain.addressAdd(&coinbase, 0); err != nil { return nil, err } - chain.apply = chain.ApplyIssuance + chain.apply = chain.applyIssuance if err := chain.Apply(eb, dbKeyMR); err != nil { return nil, err } @@ -93,51 +98,78 @@ func OpenNew(eb factom.EBlock, dbKeyMR *factom.Bytes32, networkID factom.Network return chain, nil } -// Open an existing chain database. func Open(fname string) (*Chain, error) { chain, err := open(fname) if err != nil { return nil, err } - // Load NameIDs, so load the first entry. - first, err := SelectEntryByID(chain.Conn, 1) - if err != nil { + if err := chain.loadMetadata(); err != nil { return nil, err } - if !first.IsPopulated() { - // A database must always have at least one EBlock. - return nil, fmt.Errorf("no first entry") - } - nameIDs := first.ExtIDs - if !fat.ValidTokenNameIDs(nameIDs) { - return nil, fmt.Errorf("invalid token chain Name IDs") + return chain, nil +} + +func OpenAll() (chains []*Chain, err error) { + log = _log.New("pkg", "db") + // Try to create the database directory in case it doesn't already + // exist. + if err := os.Mkdir(flag.DBPath, 0755); err != nil { + if !os.IsExist(err) { + return nil, fmt.Errorf("os.Mkdir(%#v): %v", flag.DBPath, err) + } + log.Debug("Using existing database directory...") } - chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) - // Load Chain Head - eb, dbKeyMR, err := SelectLatestEBlock(chain.Conn) + defer func() { + if err != nil { + for _, chain := range chains { + chain.Close() + } + chains = nil + } + }() + + // Scan through all files within the database directory. Ignore invalid + // file names. + files, err := ioutil.ReadDir(flag.DBPath) if err != nil { - return nil, err + return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) } - if !eb.IsPopulated() { - // A database must always have at least one EBlock. - return nil, fmt.Errorf("no eblock in database") - } - chain.Head = eb - chain.DBKeyMR = &dbKeyMR - chain.ID = eb.ChainID - - if err := chain.LoadMetadata(); err != nil { - return nil, err + chains = make([]*Chain, 0, len(files)) + for _, f := range files { + fname := f.Name() + chainID, err := fnameToChainID(fname) + if err != nil { + continue + } + log.Debugf("Loading chain: %v", chainID) + chain, err := Open(fname) + if err != nil { + return nil, err + } + if *chainID != *chain.ID { + return nil, fmt.Errorf("chain id does not match filename") + } + chains = append(chains, chain) } - - return chain, nil + return chains, nil +} +func fnameToChainID(fname string) (*factom.Bytes32, error) { + invalidFNameErr := fmt.Errorf("invalid filename: %v", fname) + if len(fname) != dbFileNameLen || + fname[dbFileNameLen-len(dbFileExtension):dbFileNameLen] != + dbFileExtension { + return nil, invalidFNameErr + } + chainID := factom.NewBytes32FromString(fname[0:64]) + if chainID == nil { + return nil, invalidFNameErr + } + return chainID, nil } -// open a READWRITE Conn and a READ_ONLY Pool for flag.DBPath + "/" + fname. -// Validate or apply schema. func open(fname string) (*Chain, error) { const baseFlags = sqlite.SQLITE_OPEN_WAL | sqlite.SQLITE_OPEN_URI | @@ -163,255 +195,12 @@ func open(fname string) (*Chain, error) { }, nil } -// Close the Conn and Pool. Log any errors. func (chain *Chain) Close() { - if err := chain.Conn.Close(); err != nil { - chain.Log.Errorf("chain.Conn.Close(): %v", err) - } if err := chain.Pool.Close(); err != nil { chain.Log.Errorf("chain.Pool.Close(): %v", err) } -} - -// Apply save the EBlock and all Entries and updates the chain state according -// to the FAT-0 and FAT-1 protocols. -func (chain *Chain) Apply(eb factom.EBlock, dbKeyMR *factom.Bytes32) (err error) { - defer sqlitex.Save(chain.Conn)(&err) - if err := InsertEBlock(chain.Conn, eb, dbKeyMR); err != nil { - return err - } - chain.Head = eb - for _, e := range eb.Entries { - if err := chain.apply(e); err != nil { - return err - } - } - return nil -} - -func (chain *Chain) ApplyIssuance(e factom.Entry) error { - eid, err := InsertEntry(chain.Conn, e, chain.Head.Sequence) - if err != nil { - return err - } - issuance := fat.NewIssuance(e) - if err = issuance.Validate(chain.ID1); err != nil { - chain.Log.Debugf("Entry{%v}: invalid issuance: %v", e.Hash, err) - return nil - } - // check sig and is valid - if err := SaveInitEntryID(chain.Conn, eid); err != nil { - return err - } - chain.Issuance = issuance - chain.SetApplyFunc() - chain.Log.Debugf("Valid Issuance Entry: %v %+v", e.Hash, issuance) - return nil -} - -func (chain *Chain) ApplyFAT0Tx(e factom.Entry) (tx fat0.Transaction, err error) { - tx = fat0.NewTransaction(e) - valid, eID, err := chain.ApplyTx(e, &tx) - if err != nil { - return - } - if !valid { - return - } - - // Do not return, but log, any errors past this point as they are - // related to being unable to apply a transaction. - defer func() { - if err != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, err) - err = nil - } else { - var cbStr string - if tx.IsCoinbase() { - cbStr = "Coinbase " - } - chain.Log.Debugf("Valid %v %vTransaction: %v %+v", - chain.Type, cbStr, tx.Hash, tx) - } - }() - // But first rollback on any error. - defer sqlitex.Save(chain.Conn)(&err) - - if err = MarkEntryValid(chain.Conn, eID); err != nil { - return - } - - for adr, amount := range tx.Outputs { - var aID int64 - aID, err = AddressAdd(chain.Conn, &adr, amount) - if err != nil { - return - } - if err = InsertAddressTransaction(chain.Conn, - aID, eID, true); err != nil { - return - } - } - - if tx.IsCoinbase() { - addIssued := tx.Inputs[fat.Coinbase()] - if chain.Supply > 0 && - int64(chain.NumIssued+addIssued) > chain.Supply { - err = fmt.Errorf("coinbase exceeds max supply") - return - } - if err = InsertAddressTransaction(chain.Conn, - 1, eID, false); err != nil { - return - } - err = chain.IncrementNumIssued(addIssued) - return - } - - for adr, amount := range tx.Inputs { - var aID int64 - aID, err = AddressSub(chain.Conn, &adr, amount) - if err != nil { - return - } - if err = InsertAddressTransaction(chain.Conn, - aID, eID, false); err != nil { - return - } - } - return -} - -func (chain *Chain) ApplyTx(e factom.Entry, tx fat.Validator) (bool, int64, error) { - eID, err := InsertEntry(chain.Conn, e, chain.Head.Sequence) - if err != nil { - return false, eID, err - } - if err := tx.Validate(chain.ID1); err != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, err) - return false, eID, nil - } - valid, err := CheckEntryUniqueValid(chain.Conn, eID, e.Hash) - if err != nil { - return false, eID, err - } - if !valid { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, "replay") - } - return valid, eID, nil -} - -func (chain *Chain) SetApplyFunc() { - // Adapt to match ApplyFunc. - switch chain.Type { - case fat0.Type: - chain.apply = func(e factom.Entry) error { - _, err := chain.ApplyFAT0Tx(e) - return err - } - case fat1.Type: - chain.apply = func(e factom.Entry) error { - _, err := chain.ApplyFAT1Tx(e) - return err - } - default: - panic("invalid type") - } -} - -func (chain *Chain) ApplyFAT1Tx(e factom.Entry) (tx fat1.Transaction, err error) { - tx = fat1.NewTransaction(e) - valid, eID, err := chain.ApplyTx(e, &tx) - if err != nil { - return - } - if !valid { - return - } - - // Do not return, but log, any errors past this point as they are - // related to being unable to apply a transaction. - defer func() { - if err != nil { - chain.Log.Debugf("Entry{%v}: invalid %v transaction: %v", - e.Hash, chain.Type, err) - err = nil - } else { - var cbStr string - if tx.IsCoinbase() { - cbStr = "Coinbase " - } - chain.Log.Debugf("Valid %v %vTransaction: %v %+v", - chain.Type, cbStr, tx.Hash, tx) - } - }() - // But first rollback on any error. - defer sqlitex.Save(chain.Conn)(&err) - - if err = MarkEntryValid(chain.Conn, eID); err != nil { - return - } - - for adr, nfTkns := range tx.Outputs { - var aID int64 - aID, err = AddressAdd(chain.Conn, &adr, uint64(len(nfTkns))) - if err != nil { - return - } - if err = InsertAddressTransaction(chain.Conn, - aID, eID, true); err != nil { - return - } - for nfID := range nfTkns { - if err = SetNFTokenOwner(chain.Conn, nfID, aID, eID); err != nil { - return - } - if err = InsertNFTokenTransaction(chain.Conn, - nfID, eID, aID); err != nil { - return - } - } - } - - if tx.IsCoinbase() { - nfTkns := tx.Inputs[fat.Coinbase()] - addIssued := uint64(len(nfTkns)) - if chain.Supply > 0 && - int64(chain.NumIssued+addIssued) > chain.Supply { - err = fmt.Errorf("coinbase exceeds max supply") - return - } - if err = InsertAddressTransaction(chain.Conn, - 1, eID, false); err != nil { - return - } - for nfID := range nfTkns { - metadata := tx.TokenMetadata[nfID] - if len(metadata) == 0 { - continue - } - if err = AttachNFTokenMetadata(chain.Conn, - nfID, metadata); err != nil { - return - } - } - err = chain.IncrementNumIssued(addIssued) - return - } - - for adr, nfTkns := range tx.Inputs { - var aID int64 - aID, err = AddressSub(chain.Conn, &adr, uint64(len(nfTkns))) - if err != nil { - return - } - if err = InsertAddressTransaction(chain.Conn, - aID, eID, false); err != nil { - return - } + // Close this last so that the wal and shm files are removed. + if err := chain.Conn.Close(); err != nil { + chain.Log.Errorf("chain.Conn.Close(): %v", err) } - return } diff --git a/db/db_test.go b/db/chain_test.go similarity index 87% rename from db/db_test.go rename to db/chain_test.go index 93d0217..dd4afdf 100644 --- a/db/db_test.go +++ b/db/chain_test.go @@ -1,7 +1,6 @@ package db import ( - "fmt" "testing" "github.com/Factom-Asset-Tokens/fatd/flag" @@ -9,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestValidate(t *testing.T) { +func TestChainValidate(t *testing.T) { require := require.New(t) flag.DBPath = "./test-fatd.db" flag.LogDebug = true @@ -18,7 +17,7 @@ func TestValidate(t *testing.T) { require.NotEmptyf(chains, "Test database is empty: %v", flag.DBPath) for _, chain := range chains { - fmt.Printf("%+v\n", chain) + defer chain.Close() assert.NoErrorf(t, chain.Validate(), "Chain{%v}.Validate()", chain.ID) } } diff --git a/db/db.go b/db/db.go deleted file mode 100644 index ab9e32f..0000000 --- a/db/db.go +++ /dev/null @@ -1,83 +0,0 @@ -package db - -import ( - "fmt" - "io/ioutil" - "os" - - "github.com/Factom-Asset-Tokens/fatd/factom" - "github.com/Factom-Asset-Tokens/fatd/flag" - _log "github.com/Factom-Asset-Tokens/fatd/log" -) - -var ( - log _log.Log -) - -const ( - dbDriver = "sqlite3" - dbFileExtension = ".sqlite3" - dbFileNameLen = len(factom.Bytes32{})*2 + len(dbFileExtension) - - PoolSize = 10 -) - -func OpenAll() (chains []*Chain, err error) { - log = _log.New("pkg", "db") - // Try to create the database directory in case it doesn't already - // exist. - if err := os.Mkdir(flag.DBPath, 0755); err != nil { - if !os.IsExist(err) { - return nil, fmt.Errorf("os.Mkdir(%#v): %v", flag.DBPath, err) - } - log.Debug("Using existing database directory...") - } - - defer func() { - if err != nil { - for _, chain := range chains { - chain.Close() - } - chains = nil - } - }() - - // Scan through all files within the database directory. Ignore invalid - // file names. - files, err := ioutil.ReadDir(flag.DBPath) - if err != nil { - return nil, fmt.Errorf("ioutil.ReadDir(%q): %v", flag.DBPath, err) - } - chains = make([]*Chain, 0, len(files)) - for _, f := range files { - fname := f.Name() - chainID, err := fnameToChainID(fname) - if err != nil { - log.Debug(err) - continue - } - log.Debugf("Loading chain: %v", chainID) - chain, err := Open(fname) - if err != nil { - return nil, err - } - if *chainID != *chain.ID { - return nil, fmt.Errorf("chain id does not match filename") - } - chains = append(chains, chain) - } - return chains, nil -} -func fnameToChainID(fname string) (*factom.Bytes32, error) { - invalidFNameErr := fmt.Errorf("invalid filename: %v", fname) - if len(fname) != dbFileNameLen || - fname[dbFileNameLen-len(dbFileExtension):dbFileNameLen] != - dbFileExtension { - return nil, invalidFNameErr - } - chainID := factom.NewBytes32FromString(fname[0:64]) - if chainID == nil { - return nil, invalidFNameErr - } - return chainID, nil -} diff --git a/db/eblock.go b/db/eblock.go index 6b9edbc..b155543 100644 --- a/db/eblock.go +++ b/db/eblock.go @@ -8,11 +8,9 @@ import ( "github.com/Factom-Asset-Tokens/fatd/factom" ) -// InsertEBlock inserts eb only if it is the next factom.EBlock in the -// sequence. -func InsertEBlock(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) error { +func (chain *Chain) insertEBlock(eb factom.EBlock, dbKeyMR *factom.Bytes32) error { // Ensure that this is the next EBlock. - prevKeyMR, err := SelectKeyMR(conn, eb.Sequence-1) + prevKeyMR, err := SelectKeyMR(chain.Conn, eb.Sequence-1) if *eb.PrevKeyMR != prevKeyMR { return fmt.Errorf("invalid EBlock{}.PrevKeyMR") } @@ -22,7 +20,7 @@ func InsertEBlock(conn *sqlite.Conn, eb factom.EBlock, dbKeyMR *factom.Bytes32) if err != nil { return fmt.Errorf("factom.EBlock{}.MarshalBinary(): %v", err) } - stmt := conn.Prep(`INSERT INTO eblocks + stmt := chain.Conn.Prep(`INSERT INTO eblocks (seq, key_mr, db_height, db_key_mr, timestamp, data) VALUES (?, ?, ?, ?, ?, ?);`) stmt.BindInt64(1, int64(eb.Sequence)) diff --git a/db/entry.go b/db/entry.go index 6e38e48..3640abf 100644 --- a/db/entry.go +++ b/db/entry.go @@ -8,14 +8,13 @@ import ( "github.com/Factom-Asset-Tokens/fatd/factom" ) -// InsertEntry inserts e and returns the id if successful. -func InsertEntry(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) { +func (chain *Chain) insertEntry(e factom.Entry, ebSeq uint32) (int64, error) { data, err := e.MarshalBinary() if err != nil { return -1, fmt.Errorf("factom.Entry{}.MarshalBinary(): %v", err) } - stmt := conn.Prep(`INSERT INTO entries + stmt := chain.Conn.Prep(`INSERT INTO entries (eb_seq, timestamp, hash, data) VALUES (?, ?, ?, ?);`) stmt.BindInt64(1, int64(int32(ebSeq))) // Preserve uint32(-1) as -1 @@ -26,26 +25,18 @@ func InsertEntry(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) if _, err := stmt.Step(); err != nil { return 0, err } - return conn.LastInsertRowID(), nil + return chain.Conn.LastInsertRowID(), nil } -// MarkEntryValid sets valid to true for the entry with the given id. -func MarkEntryValid(conn *sqlite.Conn, id int64) error { - stmt := conn.Prep(`UPDATE entries SET valid = 1 WHERE id = ?;`) +func (chain *Chain) setEntryValid(id int64) error { + stmt := chain.Conn.Prep(`UPDATE entries SET valid = 1 WHERE id = ?;`) stmt.BindInt64(1, id) _, err := stmt.Step() return err } -// SelectEntryWhere is a SQL fragment that must be appended with the condition -// of a WHERE clause and a final semi-colon. const SelectEntryWhere = `SELECT hash, data, timestamp FROM entries WHERE ` -// SelectEntry uses stmt to populate and return a new factom.Entry and whether -// it is marked as valid. Since column position is used to address the data, -// the stmt must start with `SELECT hash, data, timestamp`. This can be called -// repeatedly until stmt.Step() returns false, in which case the returned -// factom.Entry will not be populated. func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { var e factom.Entry hasRow, err := stmt.Step() @@ -73,32 +64,24 @@ func SelectEntry(stmt *sqlite.Stmt) (factom.Entry, error) { return e, nil } -// SelectEntryByID returns the factom.Entry with the given id. func SelectEntryByID(conn *sqlite.Conn, id int64) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `id = ?;`) stmt.BindInt64(1, id) return SelectEntry(stmt) } -// SelectEntryByHash returns the factom.Entry with the given hash. func SelectEntryByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `hash = ?;`) return SelectEntry(stmt) } -// SelectValidEntryByHash returns the factom.Entry with the given hash only if -// it is marked as valid. func SelectValidEntryByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) { stmt := conn.Prep(SelectEntryWhere + `hash = ? AND valid = true;`) return SelectEntry(stmt) } -// CheckEntryUniqueValid checks if there have been any Entries earlier than id -// with the same hash that are valid. If so, then this Entry is a replay and -// should be ignored. Note that if the entry hash has already been saved but -// was invalid at the time, then the entry may be valid now. -func CheckEntryUniqueValid(conn *sqlite.Conn, +func checkEntryUniqueValid(conn *sqlite.Conn, id int64, hash *factom.Bytes32) (bool, error) { stmt := conn.Prep(`SELECT count(*) FROM entries WHERE valid = true AND id < ? AND hash = ?;`) diff --git a/db/gentestdb.go b/db/gentestdb.go index da0f1e8..948b6f2 100644 --- a/db/gentestdb.go +++ b/db/gentestdb.go @@ -66,7 +66,6 @@ func main() { if err := identity.Get(c); err != nil { log.Fatal(err) } - fmt.Println(identity) // We don't need the actual dbKeyMR chain, err := db.OpenNew(first, dblock.KeyMR, Mainnet(), identity) diff --git a/db/metadata.go b/db/metadata.go index 3290014..7123f6f 100644 --- a/db/metadata.go +++ b/db/metadata.go @@ -5,9 +5,10 @@ import ( "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/fatd/factom" + "github.com/Factom-Asset-Tokens/fatd/fat" ) -func (chain *Chain) InsertMetadata() error { +func (chain *Chain) insertMetadata() error { stmt := chain.Conn.Prep(`INSERT INTO metadata (id, sync_height, sync_db_key_mr, network_id, id_key_entry, id_key_height) VALUES (0, ?, ?, ?, ?, ?);`) @@ -30,7 +31,7 @@ func (chain *Chain) InsertMetadata() error { return err } -func (chain *Chain) SaveSync() error { +func (chain *Chain) SetSyncHeight() error { stmt := chain.Conn.Prep(`UPDATE metadata SET (sync_height, sync_db_key_mr) = (?, ?) WHERE id = 0;`) stmt.BindInt64(1, int64(chain.SyncHeight)) @@ -42,18 +43,18 @@ func (chain *Chain) SaveSync() error { return err } -func SaveInitEntryID(conn *sqlite.Conn, id int64) error { - stmt := conn.Prep(`UPDATE metadata SET +func (chain *Chain) setInitEntryID(id int64) error { + stmt := chain.Conn.Prep(`UPDATE metadata SET (init_entry_id, num_issued) = (?, 0) WHERE id = 0;`) stmt.BindInt64(1, id) _, err := stmt.Step() - if conn.Changes() == 0 { + if chain.Conn.Changes() == 0 { panic("nothing updated") } return err } -func (chain *Chain) IncrementNumIssued(add uint64) error { +func (chain *Chain) numIssuedAdd(add uint64) error { stmt := chain.Conn.Prep(`UPDATE metadata SET num_issued = num_issued + ? WHERE id = 0;`) stmt.BindInt64(1, int64(add)) @@ -65,7 +66,35 @@ func (chain *Chain) IncrementNumIssued(add uint64) error { return err } -func (chain *Chain) LoadMetadata() error { +func (chain *Chain) loadMetadata() error { + // Load NameIDs + first, err := SelectEntryByID(chain.Conn, 1) + if err != nil { + return err + } + if !first.IsPopulated() { + return fmt.Errorf("no first entry") + } + + nameIDs := first.ExtIDs + if !fat.ValidTokenNameIDs(nameIDs) { + return fmt.Errorf("invalid token chain Name IDs") + } + chain.TokenID, chain.IssuerChainID = fat.TokenIssuer(nameIDs) + + // Load Chain Head + eb, dbKeyMR, err := SelectLatestEBlock(chain.Conn) + if err != nil { + return err + } + if !eb.IsPopulated() { + // A database must always have at least one EBlock. + return fmt.Errorf("no eblock in database") + } + chain.Head = eb + chain.DBKeyMR = &dbKeyMR + chain.ID = eb.ChainID + stmt := chain.Conn.Prep(`SELECT sync_height, sync_db_key_mr, network_id, id_key_entry, id_key_height, init_entry_id, num_issued FROM metadata;`) hasRow, err := stmt.Step() @@ -116,7 +145,7 @@ func (chain *Chain) LoadMetadata() error { if err := chain.Issuance.Validate(chain.ID1); err != nil { return err } - chain.SetApplyFunc() + chain.setApplyFunc() chain.NumIssued = uint64(stmt.ColumnInt64(6)) diff --git a/db/nftoken.go b/db/nftoken.go index 00404b2..b60abf6 100644 --- a/db/nftoken.go +++ b/db/nftoken.go @@ -3,12 +3,11 @@ package db import ( "encoding/json" - "crawshaw.io/sqlite" "github.com/Factom-Asset-Tokens/fatd/fat/fat1" ) -func SetNFTokenOwner(conn *sqlite.Conn, nfID fat1.NFTokenID, aID, eID int64) error { - stmt := conn.Prep(`INSERT INTO nf_tokens +func (chain *Chain) setNFTokenOwner(nfID fat1.NFTokenID, aID, eID int64) error { + stmt := chain.Conn.Prep(`INSERT INTO nf_tokens (id, owner_id, creation_entry_id) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET owner_id = excluded.owner_id;`) @@ -19,22 +18,21 @@ func SetNFTokenOwner(conn *sqlite.Conn, nfID fat1.NFTokenID, aID, eID int64) err return err } -func AttachNFTokenMetadata(conn *sqlite.Conn, +func (chain *Chain) setNFTokenMetadata( nfID fat1.NFTokenID, metadata json.RawMessage) error { - stmt := conn.Prep(`UPDATE nf_tokens + stmt := chain.Conn.Prep(`UPDATE nf_tokens SET metadata = ? WHERE id = ?;`) stmt.BindBytes(1, metadata) stmt.BindInt64(2, int64(nfID)) _, err := stmt.Step() - if conn.Changes() == 0 { + if chain.Conn.Changes() == 0 { panic("no NFTokenID updated") } return err } -func InsertNFTokenTransaction(conn *sqlite.Conn, - nfID fat1.NFTokenID, eID, aID int64) error { - stmt := conn.Prep(`INSERT INTO nf_token_transactions +func (chain *Chain) insertNFTokenTransaction(nfID fat1.NFTokenID, eID, aID int64) error { + stmt := chain.Conn.Prep(`INSERT INTO nf_token_transactions (entry_id, nf_token_id, owner_id) VALUES (?, ?, ?);`) stmt.BindInt64(1, eID) stmt.BindInt64(2, int64(nfID)) diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-shm b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-shm deleted file mode 100644 index 849be6922238ea1db6213fe40813655c4d14a584..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI5XK+@64CX+`P5dS?lHZK8bnt;XPq@h`+|) zndHWWoZcf3WhZnve7W=WEit)K?Qbs0O1{-BG04wz)PK(g{=8k_$$t&+z4v(j;iLp9 zNlK9j5=lyvC=yM|kQfq6;z(H%Ps)+XJuEJ@OdwzR|}?1JaN*B8^EC(v&nK%}EQ= zk~~44B&|paNhN6{on(+qQdCEQjthP_UM??-m%}?_VBWpVDmcgcE(5ayy##WHdS)2C zi_2Z`ti}C5+V-6@zCPN3HlPh?1KNN#pbcmP+JH8o4QK<}fHt5FXam}SHlPh?1KNN# zpbcmP+JH8o4QK<}fHt5FXam}SHlPh?1KNN#pbcmP+JH8o4QK<}fHt5FXam}SHlPh? z1KNN#pbcmP+JH8o4HVMAGdNSoTYYtR1F6_^_sjp~o8id9hui^i?LM6@jJpXwyHDQ1 z**=S(gA+LW=``dxn?1PT;oLk@o!diR!DKAJ=fR0ZKiw0*i2B)UmX0?u6Q5wOy%B^? zUV=M!lDIc#1g2miz7TRXvFU|hO=J#gGO*9V)FzQja@$lgH%5&_HWuMalMzCi-uO+( z$#$De?6*CF(D_SoH(3qtN_!Pku^3+oxtiGY!S5zA2Q{s6z`@iekt4WauqL+zzJ|B4 z1RG682x_rPvg5KXZeAIBPC;O4$~( zu?pWimDwz8X>OIR&CRf{<6SJnX0st8s^@S{#K(SAQE zL(tFMY7j1)i=9$-#&M_enum?&EpI!`<$}t=_*>A;W~(mv$>!q=p1mCJH{2!YXKpnF zx#nW0lwC0wYjDD;%w}QB^JdR)%%sAqm++6Uqpfz`Fah(h7AI{DAa?EwywmnuF*iR( zLopHau?~5DhB%?^j#EzL^dGV!Z*!i3mH5v8s1Q#WhDrDU>v1K-KK76Afz$Sf5J`}L WO1vFC1-nJu{FuCqtA6CZwf_RLBdR0- diff --git a/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-wal b/db/test-fatd.db/0692c0f9c3171575cf53d6d8067139bad3f56169f94966341018b1950542f3dd.sqlite3-wal deleted file mode 100644 index f1ccf7ecdcff857c9d7ff084f8a21413e4e9bf50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 943512 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