// Returns the transaction with the given id func (db *explorerDB) getTransaction(id crypto.Hash) (modules.TransactionResponse, error) { var tr modules.TransactionResponse // Look up the transaction's location tBytes, err := db.GetFromBucket("Transactions", encoding.Marshal(id)) if err != nil { return tr, err } var tLocation txInfo err = encoding.Unmarshal(tBytes, &tLocation) if err != nil { return tr, err } // Look up the block specified by the location and extract the transaction bBytes, err := db.GetFromBucket("Blocks", encoding.Marshal(tLocation.BlockID)) if err != nil { return tr, err } var block types.Block err = encoding.Unmarshal(bBytes, &block) if err != nil { return tr, err } tr.Tx = block.Transactions[tLocation.TxNum] tr.ParentID = tLocation.BlockID tr.TxNum = tLocation.TxNum tr.ResponseType = responseTransaction return tr, nil }
// RegisterTransaction takes a transaction and its parents and returns a // TransactionBuilder which can be used to expand the transaction. The most // typical call is 'RegisterTransaction(types.Transaction{}, nil)', which // registers a new transaction without parents. func (w *Wallet) RegisterTransaction(t types.Transaction, parents []types.Transaction) modules.TransactionBuilder { // Create a deep copy of the transaction and parents by encoding them. A // deep copy ensures that there are no pointer or slice related errors - // the builder will be working directly on the transaction, and the // transaction may be in use elsewhere (in this case, the host is using the // transaction. pBytes := encoding.Marshal(parents) var pCopy []types.Transaction err := encoding.Unmarshal(pBytes, &pCopy) if err != nil { panic(err) } tBytes := encoding.Marshal(t) var tCopy types.Transaction err = encoding.Unmarshal(tBytes, &tCopy) if err != nil { panic(err) } return &transactionBuilder{ parents: pCopy, transaction: tCopy, wallet: w, } }
// addFileContract adds a file contract to the database. An error is returned // if the file contract is already in the database. func addFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) { // Add the file contract to the database. fcBucket := tx.Bucket(FileContracts) // Sanity check - should not be adding a zero-payout file contract. if build.DEBUG && fc.Payout.IsZero() { panic("adding zero-payout file contract") } // Sanity check - should not be adding a file contract already in the db. if build.DEBUG && fcBucket.Get(id[:]) != nil { panic("repeat file contract") } err := fcBucket.Put(id[:], encoding.Marshal(fc)) if build.DEBUG && err != nil { panic(err) } // Add an entry for when the file contract expires. expirationBucketID := append(prefixFCEX, encoding.Marshal(fc.WindowEnd)...) expirationBucket, err := tx.CreateBucketIfNotExists(expirationBucketID) if build.DEBUG && err != nil { panic(err) } err = expirationBucket.Put(id[:], []byte{}) if build.DEBUG && err != nil { panic(err) } }
// newChild creates a blockNode from a block and adds it to the parent's set of // children. The new node is also returned. It necessairly modifies the database // // TODO: newChild has a fair amount of room for optimization. func (cs *ConsensusSet) newChild(pb *processedBlock, b types.Block) *processedBlock { // Create the child node. childID := b.ID() child := &processedBlock{ Block: b, Parent: b.ParentID, Height: pb.Height + 1, Depth: pb.childDepth(), } err := cs.db.Update(func(tx *bolt.Tx) error { blockMap := tx.Bucket(BlockMap) err := cs.setChildTarget(blockMap, child) if err != nil { return err } pb.Children = append(pb.Children, childID) err = blockMap.Put(child.Block.ParentID[:], encoding.Marshal(*pb)) if err != nil { return err } return blockMap.Put(childID[:], encoding.Marshal(*child)) }) if build.DEBUG && err != nil { panic(err) } return child }
// BenchmarkEncodeEmptyBlock benchmarks encoding an empty block. // // i5-4670K, 9a90f86: 48 MB/s func BenchmarkEncodeBlock(b *testing.B) { var block Block b.SetBytes(int64(len(encoding.Marshal(block)))) for i := 0; i < b.N; i++ { encoding.Marshal(block) } }
// createDSCOBucket creates a bucket for the delayed siacoin outputs at the // input height. func createDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) error { bucketID := append(prefix_dsco, encoding.Marshal(bh)...) dscoBuckets := tx.Bucket(DSCOBuckets) err := dscoBuckets.Put(encoding.Marshal(bh), encoding.Marshal(bucketID)) if err != nil { panic(err) } _, err = tx.CreateBucket(bucketID) return err }
// SigHash returns the hash of the fields in a transaction covered by a given // signature. See CoveredFields for more details. func (t Transaction) SigHash(i int) crypto.Hash { cf := t.TransactionSignatures[i].CoveredFields var signedData []byte if cf.WholeTransaction { signedData = encoding.MarshalAll( t.SiacoinInputs, t.SiacoinOutputs, t.FileContracts, t.FileContractRevisions, t.StorageProofs, t.SiafundInputs, t.SiafundOutputs, t.MinerFees, t.ArbitraryData, t.TransactionSignatures[i].ParentID, t.TransactionSignatures[i].PublicKeyIndex, t.TransactionSignatures[i].Timelock, ) } else { for _, input := range cf.SiacoinInputs { signedData = append(signedData, encoding.Marshal(t.SiacoinInputs[input])...) } for _, output := range cf.SiacoinOutputs { signedData = append(signedData, encoding.Marshal(t.SiacoinOutputs[output])...) } for _, contract := range cf.FileContracts { signedData = append(signedData, encoding.Marshal(t.FileContracts[contract])...) } for _, revision := range cf.FileContractRevisions { signedData = append(signedData, encoding.Marshal(t.FileContractRevisions[revision])...) } for _, storageProof := range cf.StorageProofs { signedData = append(signedData, encoding.Marshal(t.StorageProofs[storageProof])...) } for _, siafundInput := range cf.SiafundInputs { signedData = append(signedData, encoding.Marshal(t.SiafundInputs[siafundInput])...) } for _, siafundOutput := range cf.SiafundOutputs { signedData = append(signedData, encoding.Marshal(t.SiafundOutputs[siafundOutput])...) } for _, minerFee := range cf.MinerFees { signedData = append(signedData, encoding.Marshal(t.MinerFees[minerFee])...) } for _, arbData := range cf.ArbitraryData { signedData = append(signedData, encoding.Marshal(t.ArbitraryData[arbData])...) } } for _, sig := range cf.TransactionSignatures { signedData = append(signedData, encoding.Marshal(t.TransactionSignatures[sig])...) } return crypto.HashBytes(signedData) }
// insertItem inserts an item to a bucket. In debug mode, a panic is thrown if // the bucket does not exist or if the item is already in the bucket. func insertItem(tx *bolt.Tx, bucket []byte, key, value interface{}) error { b := tx.Bucket(bucket) if build.DEBUG && b == nil { panic(errNilBucket) } k := encoding.Marshal(key) v := encoding.Marshal(value) if build.DEBUG && b.Get(k) != nil { panic(errRepeatInsert) } return b.Put(k, v) }
func (tx *boltTx) putObject(bucket string, key, val interface{}) { // if an error has already be encountered, do nothing if tx.err != nil { return } b := tx.Bucket([]byte(bucket)) if b == nil { tx.err = errors.New("bucket does not exist: " + bucket) return } tx.err = b.Put(encoding.Marshal(key), encoding.Marshal(val)) return }
// BenchmarkStandaloneValid times how long it takes to verify a single // large transaction, with a certain number of signatures func BenchmarkStandaloneValid(b *testing.B) { numSigs := 7 // make a transaction numSigs with valid inputs with valid signatures b.ReportAllocs() txn := Transaction{} sk := make([]crypto.SecretKey, numSigs) pk := make([]crypto.PublicKey, numSigs) for i := 0; i < numSigs; i++ { s, p, err := crypto.GenerateKeyPair() if err != nil { b.Fatal(err) } sk[i] = s pk[i] = p uc := UnlockConditions{ PublicKeys: []SiaPublicKey{ {Algorithm: SignatureEd25519, Key: pk[i][:]}, }, SignaturesRequired: 1, } txn.SiacoinInputs = append(txn.SiacoinInputs, SiacoinInput{ UnlockConditions: uc, }) copy(txn.SiacoinInputs[i].ParentID[:], encoding.Marshal(i)) txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{ CoveredFields: CoveredFields{WholeTransaction: true}, }) copy(txn.TransactionSignatures[i].ParentID[:], encoding.Marshal(i)) } // Transaction must be constructed before signing for i := 0; i < numSigs; i++ { sigHash := txn.SigHash(i) sig0, err := crypto.SignHash(sigHash, sk[i]) if err != nil { b.Fatal(err) } txn.TransactionSignatures[i].Signature = sig0[:] } b.ResetTimer() for i := 0; i < b.N; i++ { err := txn.StandaloneValid(10) if err != nil { b.Fatal(err) } } }
// setSiafundPool updates the saved siafund pool on disk func setSiafundPool(tx *bolt.Tx, c types.Currency) { bucket := tx.Bucket(SiafundPool) err := bucket.Put(SiafundPool, encoding.Marshal(c)) if build.DEBUG && err != nil { panic(err) } }
// applyFileContractMaintenance looks for all of the file contracts that have // expired without an appropriate storage proof, and calls 'applyMissedProof' // for the file contract. func applyFileContractMaintenance(tx *bolt.Tx, pb *processedBlock) { // Get the bucket pointing to all of the expiring file contracts. fceBucketID := append(prefixFCEX, encoding.Marshal(pb.Height)...) fceBucket := tx.Bucket(fceBucketID) // Finish if there are no expiring file contracts. if fceBucket == nil { return } var dscods []modules.DelayedSiacoinOutputDiff var fcds []modules.FileContractDiff err := fceBucket.ForEach(func(keyBytes, valBytes []byte) error { var id types.FileContractID copy(id[:], keyBytes) amspDSCODS, fcd := applyMissedStorageProof(tx, pb, id) fcds = append(fcds, fcd) dscods = append(dscods, amspDSCODS...) return nil }) if build.DEBUG && err != nil { panic(err) } for _, dscod := range dscods { pb.DelayedSiacoinOutputDiffs = append(pb.DelayedSiacoinOutputDiffs, dscod) commitDelayedSiacoinOutputDiff(tx, dscod, modules.DiffApply) } for _, fcd := range fcds { pb.FileContractDiffs = append(pb.FileContractDiffs, fcd) commitFileContractDiff(tx, fcd, modules.DiffApply) } err = tx.DeleteBucket(fceBucketID) if build.DEBUG && err != nil { panic(err) } }
func TestLowFeeTransaction(t *testing.T) { if testing.Short() { t.SkipNow() } // Initialize variables to populate transaction pool tpt := newTpoolTester("TestLowFeeTransaction", t) emptyData := make([]byte, 15e3-16) randData := make([]byte, 16) // not yet random emptyTxn := types.Transaction{ ArbitraryData: []string{"NonSia" + string(append(emptyData, randData...))}, } transSize := len(encoding.Marshal(emptyTxn)) // Fill it to 20 MB for i := 0; i <= (TransactionPoolSizeForFee / transSize); i++ { // Make a unique transaction to accept rand.Read(randData) uniqueTxn := types.Transaction{ ArbitraryData: []string{"NonSia" + string(append(emptyData, randData...))}, } // Accept said transaction err := tpt.tpool.AcceptTransaction(uniqueTxn) if err != nil { t.Error(err) } } // Should be the straw to break the camel's back (i.e. the transaction at >20 MB) err := tpt.tpool.AcceptTransaction(emptyTxn) if err != ErrLowMinerFees { t.Fatal("expecting ErrLowMinerFees got:", err) } }
// createDSCOBucket creates a bucket for the delayed siacoin outputs at the // input height. func createDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) { bucketID := append(prefixDSCO, encoding.Marshal(bh)...) _, err := tx.CreateBucket(bucketID) if build.DEBUG && err != nil { panic(err) } }
// TestFindHostAnnouncements probes the findHostAnnouncements function func TestFindHostAnnouncements(t *testing.T) { // Create a block with a host announcement. announcement := modules.PrefixHostAnnouncement + string(encoding.Marshal(modules.HostAnnouncement{})) b := types.Block{ Transactions: []types.Transaction{ types.Transaction{ ArbitraryData: []string{announcement}, }, }, } announcements := findHostAnnouncements(b) if len(announcements) != 1 { t.Error("host announcement not found in block") } // Try with an altered prefix b.Transactions[0].ArbitraryData[0] = "bad" + b.Transactions[0].ArbitraryData[0] announcements = findHostAnnouncements(b) if len(announcements) != 0 { t.Error("host announcement found when there was an invalid prefix") } // Try with an invalid host encoding. b.Transactions[0].ArbitraryData[0] = modules.PrefixHostAnnouncement + "bad" announcements = findHostAnnouncements(b) if len(announcements) != 0 { t.Error("host announcement found when there was an invalid encoding of a host announcement") } }
// addBlockMap adds a processed block to the block map. func addBlockMap(tx *bolt.Tx, pb *processedBlock) { id := pb.Block.ID() err := tx.Bucket(BlockMap).Put(id[:], encoding.Marshal(*pb)) if build.DEBUG && err != nil { panic(err) } }
// BenchmarkAddBlockMap benchmarks adding many blocks to the set database func BenchmarkAddBlockMap(b *testing.B) { b.ReportAllocs() cst, err := createConsensusSetTester("BenchmarkAddBlockMap") if err != nil { b.Fatal(err) } // create a bunch of blocks to be added blocks := make([]processedBlock, b.N) var nonce types.BlockNonce for i := 0; i < b.N; i++ { nonceBytes := encoding.Marshal(i) copy(nonce[:], nonceBytes[:8]) blocks[i] = processedBlock{ Block: types.Block{ Nonce: nonce, }, } } b.ResetTimer() for i := 0; i < b.N; i++ { cst.cs.db.addBlockMap(&blocks[i]) } }
// Returns an array of block summaries. Bounds checking should be done elsewhere func (db *explorerDB) dbBlockSummaries(start types.BlockHeight, finish types.BlockHeight) ([]modules.ExplorerBlockData, error) { summaries := make([]modules.ExplorerBlockData, int(finish-start)) err := db.View(func(tx *bolt.Tx) error { heights := tx.Bucket([]byte("Heights")) // Iterate over each block height, constructing a // summary data structure for each block for i := start; i < finish; i++ { bSummaryBytes := heights.Get(encoding.Marshal(i)) if bSummaryBytes == nil { return errors.New("Block not found in height bucket") } err := encoding.Unmarshal(bSummaryBytes, &summaries[i-start]) if err != nil { return err } } return nil }) if err != nil { return nil, err } return summaries, nil }
func addSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID, sco types.SiacoinOutput) error { siacoinOutputs := tx.Bucket(SiacoinOutputs) if build.DEBUG && siacoinOutputs.Get(id[:]) != nil { panic(errRepeatInsert) } return siacoinOutputs.Put(id[:], encoding.Marshal(sco)) }
// Broadcast calls an RPC on all of the peers in the Gateway's peer list. The // calls are run in parallel. Broadcasts are restricted to "one-way" RPCs, // which simply write an object and disconnect. This is why Broadcast takes an // interface{} instead of an RPCFunc. func (g *Gateway) Broadcast(name string, obj interface{}) { peers := g.Peers() g.log.Printf("INFO: broadcasting RPC \"%v\" to %v peers", name, len(peers)) // only encode obj once, instead of using WriteObject enc := encoding.Marshal(obj) fn := func(conn modules.PeerConn) error { return encoding.WritePrefix(conn, enc) } var wg sync.WaitGroup wg.Add(len(peers)) for _, addr := range peers { go func(addr modules.NetAddress) { err := g.RPC(addr, name, fn) if err != nil { // try one more time before giving up time.Sleep(10 * time.Second) g.RPC(addr, name, fn) } wg.Done() }(addr) } wg.Wait() }
// ReceiveUpdatedUnconfirmedTransactions will replace the current unconfirmed // set of transactions with the input transactions. func (m *Miner) ReceiveUpdatedUnconfirmedTransactions(unconfirmedTransactions []types.Transaction, _ modules.ConsensusChange) { if err := m.tg.Add(); err != nil { return } defer m.tg.Done() m.mu.Lock() defer m.mu.Unlock() // Edge case - if there are no transactions, set the block's transactions // to nil and return. if len(unconfirmedTransactions) == 0 { m.persist.UnsolvedBlock.Transactions = nil return } // Add transactions to the block until the block size limit is reached. // Transactions are assumed to be in a sensible order. var i int remainingSize := int(types.BlockSizeLimit - 5e3) for i = range unconfirmedTransactions { remainingSize -= len(encoding.Marshal(unconfirmedTransactions[i])) if remainingSize < 0 { break } } m.persist.UnsolvedBlock.Transactions = unconfirmedTransactions[:i+1] }
// announce creates an announcement transaction and submits it to the network. func (h *Host) announce(addr modules.NetAddress) error { // create the transaction that will hold the announcement var t types.Transaction id, err := h.wallet.RegisterTransaction(t) if err != nil { return err } // create and encode the announcement and add it to the arbitrary data of // the transaction. announcement := encoding.Marshal(modules.HostAnnouncement{ IPAddress: addr, }) _, _, err = h.wallet.AddArbitraryData(id, append(modules.PrefixHostAnnouncement[:], announcement...)) if err != nil { return err } t, err = h.wallet.SignTransaction(id, true) if err != nil { return err } // Add the transaction to the transaction pool. err = h.tpool.AcceptTransaction(t) if err == modules.ErrTransactionPoolDuplicate { return errors.New("you have already announced yourself") } if err != nil { return err } return nil }
// inBucket checks if an item with the given key is in the bucket func (db *setDB) inBucket(bucket []byte, key interface{}) bool { exists, err := db.Exists(bucket, encoding.Marshal(key)) if build.DEBUG && err != nil { panic(err) } return exists }
// IsStandardTransaction enforces extra rules such as a transaction size limit. // These rules can be altered without disrupting consensus. func (tp *TransactionPool) IsStandardTransaction(t types.Transaction) error { // Check that the size of the transaction does not exceed the standard // established in Standard.md. Larger transactions are a DOS vector, // because someone can fill a large transaction with a bunch of signatures // that require hashing the entire transaction. Several hundred megabytes // of hashing can be required of a verifier. Enforcing this rule makes it // more difficult for attackers to exploid this DOS vector, though a miner // with sufficient power could still create unfriendly blocks. if len(encoding.Marshal(t)) > modules.TransactionSizeLimit { return modules.ErrLargeTransaction } // Check that all public keys are of a recognized type. Need to check all // of the UnlockConditions, which currently can appear in 3 separate fields // of the transaction. Unrecognized types are ignored because a softfork // may make certain unrecognized signatures invalid, and this node cannot // tell which sigantures are the invalid ones. for _, sci := range t.SiacoinInputs { err := tp.checkUnlockConditions(sci.UnlockConditions) if err != nil { return err } } for _, fcr := range t.FileContractRevisions { err := tp.checkUnlockConditions(fcr.UnlockConditions) if err != nil { return err } } for _, sfi := range t.SiafundInputs { err := tp.checkUnlockConditions(sfi.UnlockConditions) if err != nil { return err } } // Check that all arbitrary data is prefixed using the recognized set of // prefixes. The allowed prefixes include a 'NonSia' prefix for truly // arbitrary data. Blocking all other prefixes allows arbitrary data to be // used to orchestrate more complicated soft forks in the future without // putting older nodes at risk of violating the new rules. var prefix types.Specifier for _, arb := range t.ArbitraryData { // Check for a whilelisted prefix. copy(prefix[:], arb) if prefix == modules.PrefixHostAnnouncement || prefix == modules.PrefixNonSia { continue } // COMPATv0.3.3.3 - deprecated whitelisted prefixes. strData := string(arb) if strings.HasPrefix(strData, modules.PrefixStrNonSia) { continue } return modules.ErrInvalidArbPrefix } return nil }
// acceptTransactionSet verifies that a transaction set is allowed to be in the // transaction pool, and then adds it to the transaction pool. func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction) error { if len(ts) == 0 { return errEmptySet } // Remove all transactions that have been confirmed in the transaction set. err := tp.db.Update(func(tx *bolt.Tx) error { oldTS := ts ts = []types.Transaction{} for _, txn := range oldTS { if !tp.transactionConfirmed(tx, txn.ID()) { ts = append(ts, txn) } } return nil }) if err != nil { return err } // If no transactions remain, return a dublicate error. if len(ts) == 0 { return modules.ErrDuplicateTransactionSet } // Check the composition of the transaction set, including fees and // IsStandard rules. err = tp.checkTransactionSetComposition(ts) if err != nil { return err } // Check for conflicts with other transactions, which would indicate a // double-spend. Legal children of a transaction set will also trigger the // conflict-detector. oids := relatedObjectIDs(ts) var conflicts []TransactionSetID for _, oid := range oids { conflict, exists := tp.knownObjects[oid] if exists { conflicts = append(conflicts, conflict) } } if len(conflicts) > 0 { return tp.handleConflicts(ts, conflicts) } cc, err := tp.consensusSet.TryTransactionSet(ts) if err != nil { return modules.NewConsensusConflict(err.Error()) } // Add the transaction set to the pool. setID := TransactionSetID(crypto.HashObject(ts)) tp.transactionSets[setID] = ts for _, oid := range oids { tp.knownObjects[oid] = setID } tp.transactionSetDiffs[setID] = cc tp.transactionListSize += len(encoding.Marshal(ts)) return nil }
func BenchmarkGetBlockMap(b *testing.B) { b.ReportAllocs() cst, err := createConsensusSetTester("BenchmarkGetBlockMap") if err != nil { b.Fatal(err) } // create a bunch of blocks to be added blocks := make([]processedBlock, 100) blockIDs := make([]types.BlockID, 100) var nonce types.BlockNonce for i := 0; i < 100; i++ { nonceBytes := encoding.Marshal(i) copy(nonce[:], nonceBytes[:8]) blocks[i] = processedBlock{ Block: types.Block{ Nonce: nonce, }, } blockIDs[i] = blocks[i].Block.ID() } for i := 0; i < 100; i++ { cst.cs.db.addBlockMap(&blocks[i]) } b.ResetTimer() for i := 0; i < b.N; i++ { // Just do the lookup/allocation, but don't even store cst.cs.db.getBlockMap(blockIDs[i%100]) } }
// dbGetTransactionIDSet returns a 'func(*bolt.Tx) error' that decodes a // bucket of transaction IDs into a slice. If the bucket is nil, // dbGetTransactionIDSet returns errNotExist. func dbGetTransactionIDSet(bucket []byte, key interface{}, ids *[]types.TransactionID) func(*bolt.Tx) error { return func(tx *bolt.Tx) error { b := tx.Bucket(bucket).Bucket(encoding.Marshal(key)) if b == nil { return errNotExist } // decode into a local slice var txids []types.TransactionID err := b.ForEach(func(txid, _ []byte) error { var id types.TransactionID err := encoding.Unmarshal(txid, &id) if err != nil { return err } txids = append(txids, id) return nil }) if err != nil { return err } // set pointer *ids = txids return nil } }
// announce creates an announcement transaction and submits it to the network. func (h *Host) announce(addr modules.NetAddress) error { // Generate an unlock hash, if necessary. if h.UnlockHash == (types.UnlockHash{}) { uc, err := h.wallet.NextAddress() if err != nil { return err } h.UnlockHash = uc.UnlockHash() err = h.save() if err != nil { return err } } // Create a transaction with a host announcement. txnBuilder := h.wallet.StartTransaction() announcement := encoding.Marshal(modules.HostAnnouncement{ IPAddress: addr, }) _ = txnBuilder.AddArbitraryData(append(modules.PrefixHostAnnouncement[:], announcement...)) txn, parents := txnBuilder.View() txnSet := append(parents, txn) // Add the transaction to the transaction pool. err := h.tpool.AcceptTransactionSet(txnSet) if err == modules.ErrDuplicateTransactionSet { return errors.New("you have already announced yourself") } if err != nil { return err } return nil }
// TestFindHostAnnouncements probes the findHostAnnouncements function func TestFindHostAnnouncements(t *testing.T) { // Create a block with a host announcement. announcement := append(modules.PrefixHostAnnouncement[:], encoding.Marshal(modules.HostAnnouncement{})...) b := types.Block{ Transactions: []types.Transaction{ types.Transaction{ ArbitraryData: [][]byte{announcement}, }, }, } announcements := findHostAnnouncements(b) if len(announcements) != 1 { t.Error("host announcement not found in block") } // Try with an altered prefix b.Transactions[0].ArbitraryData[0][0]++ announcements = findHostAnnouncements(b) if len(announcements) != 0 { t.Error("host announcement found when there was an invalid prefix") } b.Transactions[0].ArbitraryData[0][0]-- // Try with an invalid host encoding. b.Transactions[0].ArbitraryData[0][17]++ announcements = findHostAnnouncements(b) if len(announcements) != 0 { t.Error("host announcement found when there was an invalid encoding of a host announcement") } }
// fitsInABlock checks if the transaction is likely to fit in a block. // Currently there is no limitation on transaction size other than it must fit // in a block. func (t Transaction) fitsInABlock() error { // Check that the transaction will fit inside of a block, leaving 5kb for // overhead. if uint64(len(encoding.Marshal(t))) > BlockSizeLimit-5e3 { return ErrTransactionTooLarge } return nil }