github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/ledger.go (about) 1 package native 2 3 import ( 4 "fmt" 5 "math" 6 7 "github.com/nspcc-dev/neo-go/pkg/config" 8 "github.com/nspcc-dev/neo-go/pkg/core/dao" 9 "github.com/nspcc-dev/neo-go/pkg/core/interop" 10 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 11 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 12 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 13 "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" 14 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 15 "github.com/nspcc-dev/neo-go/pkg/util" 16 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 17 "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" 18 ) 19 20 // Ledger provides an interface to blocks/transactions storage for smart 21 // contracts. It's not a part of the proper chain's state, so it's just a 22 // proxy between regular Blockchain/DAO interface and smart contracts. 23 type Ledger struct { 24 interop.ContractMD 25 } 26 27 const ledgerContractID = -4 28 29 // newLedger creates a new Ledger native contract. 30 func newLedger() *Ledger { 31 var l = &Ledger{ 32 ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID), 33 } 34 defer l.BuildHFSpecificMD(l.ActiveIn()) 35 36 desc := newDescriptor("currentHash", smartcontract.Hash256Type) 37 md := newMethodAndPrice(l.currentHash, 1<<15, callflag.ReadStates) 38 l.AddMethod(md, desc) 39 40 desc = newDescriptor("currentIndex", smartcontract.IntegerType) 41 md = newMethodAndPrice(l.currentIndex, 1<<15, callflag.ReadStates) 42 l.AddMethod(md, desc) 43 44 desc = newDescriptor("getBlock", smartcontract.ArrayType, 45 manifest.NewParameter("indexOrHash", smartcontract.ByteArrayType)) 46 md = newMethodAndPrice(l.getBlock, 1<<15, callflag.ReadStates) 47 l.AddMethod(md, desc) 48 49 desc = newDescriptor("getTransaction", smartcontract.ArrayType, 50 manifest.NewParameter("hash", smartcontract.Hash256Type)) 51 md = newMethodAndPrice(l.getTransaction, 1<<15, callflag.ReadStates) 52 l.AddMethod(md, desc) 53 54 desc = newDescriptor("getTransactionHeight", smartcontract.IntegerType, 55 manifest.NewParameter("hash", smartcontract.Hash256Type)) 56 md = newMethodAndPrice(l.getTransactionHeight, 1<<15, callflag.ReadStates) 57 l.AddMethod(md, desc) 58 59 desc = newDescriptor("getTransactionFromBlock", smartcontract.ArrayType, 60 manifest.NewParameter("blockIndexOrHash", smartcontract.ByteArrayType), 61 manifest.NewParameter("txIndex", smartcontract.IntegerType)) 62 md = newMethodAndPrice(l.getTransactionFromBlock, 1<<16, callflag.ReadStates) 63 l.AddMethod(md, desc) 64 65 desc = newDescriptor("getTransactionSigners", smartcontract.ArrayType, 66 manifest.NewParameter("hash", smartcontract.Hash256Type)) 67 md = newMethodAndPrice(l.getTransactionSigners, 1<<15, callflag.ReadStates) 68 l.AddMethod(md, desc) 69 70 desc = newDescriptor("getTransactionVMState", smartcontract.IntegerType, 71 manifest.NewParameter("hash", smartcontract.Hash256Type)) 72 md = newMethodAndPrice(l.getTransactionVMState, 1<<15, callflag.ReadStates) 73 l.AddMethod(md, desc) 74 75 return l 76 } 77 78 // Metadata implements the Contract interface. 79 func (l *Ledger) Metadata() *interop.ContractMD { 80 return &l.ContractMD 81 } 82 83 // Initialize implements the Contract interface. 84 func (l *Ledger) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error { 85 return nil 86 } 87 88 // InitializeCache implements the Contract interface. 89 func (l *Ledger) InitializeCache(blockHeight uint32, d *dao.Simple) error { 90 return nil 91 } 92 93 // OnPersist implements the Contract interface. 94 func (l *Ledger) OnPersist(ic *interop.Context) error { 95 // Actual block/tx processing is done in Blockchain.storeBlock(). 96 // Even though C# node add them to storage here, they're not 97 // accessible to smart contracts (see isTraceableBlock()), thus 98 // the end effect is the same. 99 return nil 100 } 101 102 // PostPersist implements the Contract interface. 103 func (l *Ledger) PostPersist(ic *interop.Context) error { 104 return nil // Actual block/tx processing is done in Blockchain.storeBlock(). 105 } 106 107 // ActiveIn implements the Contract interface. 108 func (l *Ledger) ActiveIn() *config.Hardfork { 109 return nil 110 } 111 112 // currentHash implements currentHash SC method. 113 func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 114 return stackitem.Make(ic.CurrentBlockHash().BytesBE()) 115 } 116 117 // currentIndex implements currentIndex SC method. 118 func (l *Ledger) currentIndex(ic *interop.Context, _ []stackitem.Item) stackitem.Item { 119 return stackitem.Make(ic.BlockHeight()) 120 } 121 122 // getBlock implements getBlock SC method. 123 func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item { 124 hash := getBlockHashFromItem(ic, params[0]) 125 block, err := ic.GetBlock(hash) 126 if err != nil || !isTraceableBlock(ic, block.Index) { 127 return stackitem.Null{} 128 } 129 return block.ToStackItem() 130 } 131 132 // getTransaction returns transaction to the SC. 133 func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) stackitem.Item { 134 tx, h, err := getTransactionAndHeight(ic.DAO, params[0]) 135 if err != nil || !isTraceableBlock(ic, h) { 136 return stackitem.Null{} 137 } 138 return tx.ToStackItem() 139 } 140 141 // getTransactionHeight returns transaction height to the SC. 142 func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.Item) stackitem.Item { 143 _, h, err := getTransactionAndHeight(ic.DAO, params[0]) 144 if err != nil || !isTraceableBlock(ic, h) { 145 return stackitem.Make(-1) 146 } 147 return stackitem.Make(h) 148 } 149 150 // getTransactionFromBlock returns a transaction with the given index from the 151 // block with the height or hash specified. 152 func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item { 153 hash := getBlockHashFromItem(ic, params[0]) 154 index := toUint32(params[1]) 155 block, err := ic.GetBlock(hash) 156 if err != nil || !isTraceableBlock(ic, block.Index) { 157 return stackitem.Null{} 158 } 159 if index >= uint32(len(block.Transactions)) { 160 panic("wrong transaction index") 161 } 162 return block.Transactions[index].ToStackItem() 163 } 164 165 // getTransactionSigners returns transaction signers to the SC. 166 func (l *Ledger) getTransactionSigners(ic *interop.Context, params []stackitem.Item) stackitem.Item { 167 tx, h, err := getTransactionAndHeight(ic.DAO, params[0]) 168 if err != nil || !isTraceableBlock(ic, h) { 169 return stackitem.Null{} 170 } 171 return transaction.SignersToStackItem(tx.Signers) 172 } 173 174 // getTransactionVMState returns VM state got after transaction invocation. 175 func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.Item) stackitem.Item { 176 hash, err := getUint256FromItem(params[0]) 177 if err != nil { 178 panic(err) 179 } 180 h, _, aer, err := ic.DAO.GetTxExecResult(hash) 181 if err != nil || !isTraceableBlock(ic, h) { 182 return stackitem.Make(vmstate.None) 183 } 184 return stackitem.Make(aer.VMState) 185 } 186 187 // isTraceableBlock defines whether we're able to give information about 188 // the block with the index specified. 189 func isTraceableBlock(ic *interop.Context, index uint32) bool { 190 height := ic.BlockHeight() 191 MaxTraceableBlocks := ic.Chain.GetConfig().MaxTraceableBlocks 192 return index <= height && index+MaxTraceableBlocks > height 193 } 194 195 // getBlockHashFromItem converts the given stackitem.Item to a block hash using the given 196 // Ledger if needed. Interop functions accept both block numbers and 197 // block hashes as parameters, thus this function is needed. It's supposed to 198 // be called within VM context, so it panics if anything goes wrong. 199 func getBlockHashFromItem(ic *interop.Context, item stackitem.Item) util.Uint256 { 200 bigindex, err := item.TryInteger() 201 if err == nil && bigindex.IsUint64() { 202 index := bigindex.Uint64() 203 if index > math.MaxUint32 { 204 panic("bad block index") 205 } 206 if uint32(index) > ic.BlockHeight() { 207 panic(fmt.Errorf("no block with index %d", index)) 208 } 209 return ic.Chain.GetHeaderHash(uint32(index)) 210 } 211 hash, err := getUint256FromItem(item) 212 if err != nil { 213 panic(err) 214 } 215 return hash 216 } 217 218 func getUint256FromItem(item stackitem.Item) (util.Uint256, error) { 219 hashbytes, err := item.TryBytes() 220 if err != nil { 221 return util.Uint256{}, fmt.Errorf("failed to get hash bytes: %w", err) 222 } 223 hash, err := util.Uint256DecodeBytesBE(hashbytes) 224 if err != nil { 225 return util.Uint256{}, fmt.Errorf("failed to decode hash: %w", err) 226 } 227 return hash, nil 228 } 229 230 // getTransactionAndHeight returns a transaction and its height if it's present 231 // on the chain. It panics if anything goes wrong. 232 func getTransactionAndHeight(d *dao.Simple, item stackitem.Item) (*transaction.Transaction, uint32, error) { 233 hash, err := getUint256FromItem(item) 234 if err != nil { 235 panic(err) 236 } 237 return d.GetTransaction(hash) 238 }