-
Notifications
You must be signed in to change notification settings - Fork 13
/
store.go
223 lines (184 loc) · 6.54 KB
/
store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// Copyright (c) 2014-2017 Bitmark Inc.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package block
import (
"encoding/binary"
"github.com/bitmark-inc/bitmarkd/asset"
"github.com/bitmark-inc/bitmarkd/blockdigest"
"github.com/bitmark-inc/bitmarkd/blockrecord"
"github.com/bitmark-inc/bitmarkd/blockring"
"github.com/bitmark-inc/bitmarkd/currency"
"github.com/bitmark-inc/bitmarkd/currency/bitcoin"
"github.com/bitmark-inc/bitmarkd/currency/litecoin"
"github.com/bitmark-inc/bitmarkd/fault"
"github.com/bitmark-inc/bitmarkd/merkle"
"github.com/bitmark-inc/bitmarkd/mode"
"github.com/bitmark-inc/bitmarkd/reservoir"
"github.com/bitmark-inc/bitmarkd/storage"
"github.com/bitmark-inc/bitmarkd/transactionrecord"
"github.com/bitmark-inc/logger"
)
// store an incoming block checking to make sure it is valid first
func StoreIncoming(packedBlock []byte) error {
globalData.Lock()
defer globalData.Unlock()
reservoir.Disable()
defer reservoir.Enable()
packedHeader := blockrecord.PackedHeader(packedBlock[:blockrecord.TotalBlockSize])
header, err := packedHeader.Unpack()
if nil != err {
return err
}
if globalData.previousBlock != header.PreviousBlock {
return fault.ErrPreviousBlockDigestDoesNotMatch
}
data := packedBlock[blockrecord.TotalBlockSize:]
type txn struct {
packed transactionrecord.Packed
unpacked interface{}
}
txs := make([]txn, header.TransactionCount)
txIds := make([]merkle.Digest, header.TransactionCount)
// check all transactions are valid
for i := uint16(0); i < header.TransactionCount; i += 1 {
transaction, n, err := transactionrecord.Packed(data).Unpack(mode.IsTesting())
if nil != err {
return err
}
txs[i].packed = transactionrecord.Packed(data[:n])
txs[i].unpacked = transaction
txIds[i] = merkle.NewDigest(data[:n])
data = data[n:]
}
// build the tree of transaction IDs
fullMerkleTree := merkle.FullMerkleTree(txIds)
merkleRoot := fullMerkleTree[len(fullMerkleTree)-1]
if merkleRoot != header.MerkleRoot {
return fault.ErrMerkleRootDoesNotMatch
}
digest := packedHeader.Digest()
// this sets the maximum number of currencies supported
// order is determined by the currency enum order
currencyAddresses := [currency.Count]string{} // bitcoin, litecoin
// store transactions
for i, item := range txs {
txId := txIds[i]
packed := item.packed
switch tx := item.unpacked.(type) {
case *transactionrecord.BaseData:
// ensure base data is at the start of a block
if i >= len(currencyAddresses) {
return fault.ErrOutOfPlaceBaseData
}
// ensure order follows currency enum order
if tx.Currency.Index() != i {
return fault.ErrOutOfPlaceBaseData
}
// extract the currency address for payments
switch tx.Currency {
case currency.Bitcoin:
cType, _, err := bitcoin.ValidateAddress(tx.PaymentAddress)
if nil != err {
return err
}
switch cType {
case bitcoin.Testnet, bitcoin.TestnetScript:
if !mode.IsTesting() {
return fault.ErrBitcoinAddressForWrongNetwork
}
case bitcoin.Livenet, bitcoin.LivenetScript:
if mode.IsTesting() {
return fault.ErrBitcoinAddressForWrongNetwork
}
default:
return fault.ErrBitcoinAddressIsNotSupported
}
// save bitcoin address
currencyAddresses[0] = tx.PaymentAddress
// simulate a litecoin address (from this bitcoin address) as a default
// and to provide a litecoin address for older blocks with no litecoin base record
currencyAddresses[1], err = litecoin.FromBitcoin(tx.PaymentAddress)
case currency.Litecoin:
cType, _, err := litecoin.ValidateAddress(tx.PaymentAddress)
if nil != err {
return err
}
switch cType {
case litecoin.Testnet, litecoin.TestnetScript:
if !mode.IsTesting() {
return fault.ErrLitecoinAddressForWrongNetwork
}
case litecoin.Livenet, litecoin.LivenetScript, litecoin.LivenetScript2:
if mode.IsTesting() {
return fault.ErrLitecoinAddressForWrongNetwork
}
default:
return fault.ErrLitecoinAddressIsNotSupported
}
// save litecoin address
currencyAddresses[1] = tx.PaymentAddress
default:
return fault.ErrInvalidCurrency
}
case *transactionrecord.AssetData:
assetIndex := tx.AssetIndex()
key := assetIndex[:]
asset.Delete(assetIndex)
storage.Pool.Assets.Put(key, packed)
case *transactionrecord.BitmarkIssue:
key := txId[:]
reservoir.DeleteByTxId(txId)
storage.Pool.Transactions.Put(key, packed)
CreateOwnership(txId, header.Number, tx.AssetIndex, tx.Owner)
case *transactionrecord.BitmarkTransfer:
key := txId[:]
reservoir.DeleteByTxId(txId)
// when deleting a pending it is possible that the tx id
// it was holding was different to this tx id
// i.e. it is a duplicate so it also must be removed
// to prevent the possibility of a double-spend
reservoir.DeleteByLink(tx.Link)
storage.Pool.Transactions.Put(key, packed)
linkOwner := OwnerOf(tx.Link)
if nil == linkOwner {
logger.Criticalf("missing transaction record for link: %v refererenced by tx id: %v", tx.Link, txId)
logger.Panic("Transactions database is corrupt")
}
TransferOwnership(tx.Link, txId, header.Number, linkOwner, tx.Owner)
default:
globalData.log.Criticalf("unhandled transaction: %v", tx)
logger.Panicf("unhandled transaction: %v", tx)
}
}
// currency database write
blockNumber := make([]byte, 8)
binary.BigEndian.PutUint64(blockNumber, header.Number)
byteCount := 0
for _, s := range currencyAddresses {
byteCount += len(s) + 1 // include a 0x00 separator byte as each string is Base58 ASCII text
}
currencyData := make([]byte, 0, byteCount)
for _, s := range currencyAddresses {
currencyData = append(currencyData, s...)
currencyData = append(currencyData, 0x00)
}
storage.Pool.BlockOwners.Put(blockNumber, currencyData)
// finish be stoing the block header
storeAndUpdate(header, digest, packedBlock)
return nil
}
// store the block and update block data
// hold lock before calling this
func storeAndUpdate(header *blockrecord.Header, digest blockdigest.Digest, packedBlock []byte) {
expectedBlockNumber := globalData.height + 1
if expectedBlockNumber != header.Number {
logger.Panicf("block.Store: out of sequence block: actual: %d expected: %d", header.Number, expectedBlockNumber)
}
globalData.previousBlock = digest
globalData.height = header.Number
blockring.Put(header.Number, digest, packedBlock)
blockNumber := make([]byte, 8)
binary.BigEndian.PutUint64(blockNumber, header.Number)
storage.Pool.Blocks.Put(blockNumber, packedBlock)
}